Fast message grouping/sorting
Signed-off-by: Julien Eluard <julien.eluard@gmail.com>
This commit is contained in:
parent
32d3b16f83
commit
e1da12d8a2
|
@ -70,15 +70,20 @@
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:load-more-messages
|
:load-more-messages
|
||||||
[(re-frame/inject-cofx :data-store/get-messages)]
|
[(re-frame/inject-cofx :data-store/get-messages)]
|
||||||
(fn [{{:keys [current-chat-id] :as db} :db get-stored-messages :get-stored-messages} _]
|
(fn [{{:keys [current-chat-id] :as db} :db get-stored-messages :get-stored-messages :as cofx} _]
|
||||||
(when-not (get-in db [:chats current-chat-id :all-loaded?])
|
(when-not (get-in db [:chats current-chat-id :all-loaded?])
|
||||||
(let [loaded-count (count (get-in db [:chats current-chat-id :messages]))
|
(let [loaded-count (count (get-in db [:chats current-chat-id :messages]))
|
||||||
new-messages (index-messages (get-stored-messages current-chat-id loaded-count))]
|
new-messages (get-stored-messages current-chat-id loaded-count)
|
||||||
{:db (-> db
|
indexed-messages (index-messages new-messages)]
|
||||||
(update-in [:chats current-chat-id :messages] merge new-messages)
|
(handlers-macro/merge-fx
|
||||||
(update-in [:chats current-chat-id :not-loaded-message-ids] #(apply disj % (keys new-messages)))
|
cofx
|
||||||
(assoc-in [:chats current-chat-id :all-loaded?]
|
{:db (-> db
|
||||||
(> constants/default-number-of-messages (count new-messages))))}))))
|
(update-in [:chats current-chat-id :messages] merge indexed-messages)
|
||||||
|
(update-in [:chats current-chat-id :not-loaded-message-ids]
|
||||||
|
#(apply disj % (keys indexed-messages)))
|
||||||
|
(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))))))
|
||||||
|
|
||||||
(handlers/register-handler-db
|
(handlers/register-handler-db
|
||||||
:message-appeared
|
:message-appeared
|
||||||
|
@ -173,6 +178,13 @@
|
||||||
(vals contacts-to-add))]}
|
(vals contacts-to-add))]}
|
||||||
(events.loading/load-commands))))
|
(events.loading/load-commands))))
|
||||||
|
|
||||||
|
(defn- group-chat-messages
|
||||||
|
[{:keys [db]}]
|
||||||
|
(reduce-kv (fn [fx chat-id {:keys [messages]}]
|
||||||
|
(models.message/group-messages chat-id (vals messages) fx))
|
||||||
|
{:db db}
|
||||||
|
(:chats db)))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:initialize-chats
|
:initialize-chats
|
||||||
[(re-frame/inject-cofx :get-default-contacts)
|
[(re-frame/inject-cofx :get-default-contacts)
|
||||||
|
@ -206,6 +218,7 @@
|
||||||
(handlers-macro/merge-fx cofx
|
(handlers-macro/merge-fx cofx
|
||||||
{:db (assoc db :chats chats)}
|
{:db (assoc db :chats chats)}
|
||||||
(init-console-chat)
|
(init-console-chat)
|
||||||
|
(group-chat-messages)
|
||||||
(add-default-contacts)))))
|
(add-default-contacts)))))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
|
|
|
@ -44,13 +44,19 @@
|
||||||
;; regular non command message, we can add it right away
|
;; regular non command message, we can add it right away
|
||||||
(message-model/receive message cofx))))
|
(message-model/receive message cofx))))
|
||||||
|
|
||||||
(defn add-messages [[messages] {:keys [db] :as cofx}]
|
(defn add-messages [messages {:keys [db] :as cofx}]
|
||||||
(handlers-macro/merge-effects cofx add-message messages))
|
(let [messages-to-add (filter (partial message-model/add-to-chat? cofx) messages)
|
||||||
|
plain-messages (remove (comp :command :content) messages-to-add)
|
||||||
|
command-messages (filter (comp :command :content) messages-to-add)]
|
||||||
|
(handlers-macro/merge-effects (message-model/receive-many plain-messages cofx)
|
||||||
|
cofx
|
||||||
|
add-message
|
||||||
|
command-messages)))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:chat-received-message/add
|
:chat-received-message/add
|
||||||
message-model/receive-interceptors
|
message-model/receive-interceptors
|
||||||
(fn [cofx messages]
|
(fn [cofx [messages]]
|
||||||
(add-messages messages cofx)))
|
(add-messages messages cofx)))
|
||||||
|
|
||||||
;; TODO(alwx): refactor this when status-im.commands.handlers.jail is refactored
|
;; TODO(alwx): refactor this when status-im.commands.handlers.jail is refactored
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
[status-im.constants :as constants]
|
[status-im.constants :as constants]
|
||||||
[status-im.utils.core :as utils]
|
[status-im.utils.core :as utils]
|
||||||
[status-im.utils.ethereum.core :as ethereum]
|
[status-im.utils.ethereum.core :as ethereum]
|
||||||
|
[status-im.utils.datetime :as time]
|
||||||
[status-im.chat.events.console :as console-events]
|
[status-im.chat.events.console :as console-events]
|
||||||
[status-im.chat.events.requests :as requests-events]
|
[status-im.chat.events.requests :as requests-events]
|
||||||
[status-im.chat.models :as chat-model]
|
[status-im.chat.models :as chat-model]
|
||||||
|
@ -33,20 +34,77 @@
|
||||||
|
|
||||||
(defn- prepare-message
|
(defn- prepare-message
|
||||||
[{:keys [content] :as message} chat-id current-chat?]
|
[{:keys [content] :as message} chat-id current-chat?]
|
||||||
|
;; TODO janherich: enable the animations again once we can do them more efficiently
|
||||||
(cond-> (assoc message :appearing? true)
|
(cond-> (assoc message :appearing? true)
|
||||||
(not current-chat?) (assoc :appearing? false)
|
(not current-chat?) (assoc :appearing? false)
|
||||||
(emoji-only-content? content) (assoc :content-type constants/content-type-emoji)))
|
(emoji-only-content? content) (assoc :content-type constants/content-type-emoji)))
|
||||||
|
|
||||||
|
(defn- re-index-message-groups
|
||||||
|
"Relative datemarks of message groups can get obsolete with passing time,
|
||||||
|
this function re-indexes them for given chat"
|
||||||
|
[chat-id {:keys [db]}]
|
||||||
|
(let [chat-messages (get-in db [:chats chat-id :messages])]
|
||||||
|
{:db (update-in db
|
||||||
|
[:chats chat-id :message-groups]
|
||||||
|
(partial reduce-kv (fn [groups datemark message-refs]
|
||||||
|
(let [new-datemark (->> message-refs
|
||||||
|
first
|
||||||
|
:message-id
|
||||||
|
(get chat-messages)
|
||||||
|
:timestamp
|
||||||
|
time/day-relative)]
|
||||||
|
(if (= datemark new-datemark)
|
||||||
|
;; nothing to re-index
|
||||||
|
(assoc groups datemark message-refs)
|
||||||
|
;; relative datemark shifted, reindex
|
||||||
|
(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-message
|
(defn- add-message
|
||||||
[chat-id {:keys [message-id clock-value content] :as message} current-chat? {:keys [db]}]
|
[batch? {:keys [chat-id message-id clock-value content] :as message} current-chat? {:keys [db] :as cofx}]
|
||||||
(let [prepared-message (prepare-message message chat-id current-chat?)]
|
(let [prepared-message (prepare-message message chat-id current-chat?)]
|
||||||
{:db (cond->
|
(let [fx {:db (cond->
|
||||||
(-> db
|
(-> db
|
||||||
(update-in [:chats chat-id :messages] assoc message-id prepared-message)
|
(update-in [:chats chat-id :messages] assoc message-id prepared-message)
|
||||||
(update-in [:chats chat-id :last-clock-value] (partial utils.clocks/receive clock-value))) ; this will increase last-clock-value twice when sending our own messages
|
;; this will increase last-clock-value twice when sending our own messages
|
||||||
(not current-chat?)
|
(update-in [:chats chat-id :last-clock-value] (partial utils.clocks/receive clock-value)))
|
||||||
(update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id))
|
(not current-chat?)
|
||||||
:data-store/tx [(messages-store/save-message-tx prepared-message)]}))
|
(update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id))
|
||||||
|
:data-store/tx [(messages-store/save-message-tx prepared-message)]}]
|
||||||
|
(if batch?
|
||||||
|
fx
|
||||||
|
(handlers-macro/merge-fx cofx
|
||||||
|
fx
|
||||||
|
(re-index-message-groups chat-id)
|
||||||
|
(group-messages chat-id [message]))))))
|
||||||
|
|
||||||
|
(def ^:private- add-single-message (partial add-message false))
|
||||||
|
(def ^:private- add-batch-message (partial add-message true))
|
||||||
|
|
||||||
(defn- prepare-chat [chat-id {:keys [db now] :as cofx}]
|
(defn- prepare-chat [chat-id {:keys [db now] :as cofx}]
|
||||||
(chat-model/upsert-chat {:chat-id chat-id
|
(chat-model/upsert-chat {:chat-id chat-id
|
||||||
|
@ -57,9 +115,11 @@
|
||||||
(transport/send (protocol/map->MessagesSeen {:message-ids #{message-id}}) chat-id cofx)))
|
(transport/send (protocol/map->MessagesSeen {:message-ids #{message-id}}) chat-id cofx)))
|
||||||
|
|
||||||
(defn- add-received-message
|
(defn- add-received-message
|
||||||
[{:keys [from message-id chat-id content content-type timestamp clock-value to-clock-value] :as message}
|
[batch?
|
||||||
|
{:keys [from message-id chat-id content content-type timestamp clock-value to-clock-value js-obj] :as message}
|
||||||
{:keys [db now] :as cofx}]
|
{:keys [db now] :as cofx}]
|
||||||
(let [{:keys [current-chat-id
|
(let [{:keys [web3
|
||||||
|
current-chat-id
|
||||||
view-id
|
view-id
|
||||||
access-scope->commands-responses]
|
access-scope->commands-responses]
|
||||||
:contacts/keys [contacts]} db
|
:contacts/keys [contacts]} db
|
||||||
|
@ -70,42 +130,58 @@
|
||||||
request-command (:request-command content)
|
request-command (:request-command content)
|
||||||
command-request? (and (= content-type constants/content-type-command-request)
|
command-request? (and (= content-type constants/content-type-command-request)
|
||||||
request-command)
|
request-command)
|
||||||
new-timestamp (or timestamp now)]
|
new-timestamp (or timestamp now)
|
||||||
|
add-message-fn (if batch? add-batch-message add-single-message)]
|
||||||
(handlers-macro/merge-fx cofx
|
(handlers-macro/merge-fx cofx
|
||||||
(add-message chat-id
|
{:confirm-message-processed [{:web3 web3
|
||||||
(cond-> (assoc message
|
:js-obj js-obj}]}
|
||||||
:timestamp new-timestamp
|
(add-message-fn (cond-> (assoc message :timestamp new-timestamp)
|
||||||
:show? true)
|
public-key
|
||||||
public-key
|
(assoc :user-statuses {public-key (if current-chat? :seen :received)})
|
||||||
(assoc :user-statuses {public-key (if current-chat? :seen :received)})
|
(not clock-value)
|
||||||
|
(assoc :clock-value (utils.clocks/send last-clock-value)) ; TODO (cammeelos): for backward compatibility, we use received time to be removed when not an issue anymore
|
||||||
(not clock-value)
|
command-request?
|
||||||
(assoc :clock-value (utils.clocks/send last-clock-value)) ; TODO (cammeelos): for backward compatibility, we use received time to be removed when not an issue anymore
|
(assoc-in [:content :request-command-ref]
|
||||||
command-request?
|
(lookup-response-ref access-scope->commands-responses
|
||||||
(assoc-in [:content :request-command-ref]
|
current-account chat contacts request-command)))
|
||||||
(lookup-response-ref access-scope->commands-responses
|
current-chat?)
|
||||||
current-account chat contacts request-command)))
|
(requests-events/add-request chat-id message-id)
|
||||||
current-chat?)
|
|
||||||
(send-message-seen chat-id message-id (and public-key
|
(send-message-seen chat-id message-id (and public-key
|
||||||
(not public?)
|
(not public?)
|
||||||
current-chat?
|
current-chat?
|
||||||
(not (chat-model/bot-only-chat? db chat-id))
|
(not (chat-model/bot-only-chat? db chat-id))
|
||||||
(not (= constants/system from)))))))
|
(not (= constants/system from)))))))
|
||||||
|
|
||||||
(defn confirm-messages-processed [js-obj {{:keys [web3]} :db}]
|
(def ^:private add-single-received-message (partial add-received-message false))
|
||||||
{:confirm-message-processed [{:web3 web3
|
(def ^:private add-batch-received-message (partial add-received-message true))
|
||||||
:js-obj js-obj}]})
|
|
||||||
|
|
||||||
(defn receive
|
(defn receive
|
||||||
[{:keys [chat-id message-id js-obj] :as message} {:keys [now] :as cofx}]
|
[{:keys [chat-id message-id] :as message} {:keys [now] :as cofx}]
|
||||||
(handlers-macro/merge-fx cofx
|
(handlers-macro/merge-fx cofx
|
||||||
(chat-model/upsert-chat {:chat-id chat-id
|
(chat-model/upsert-chat {:chat-id chat-id
|
||||||
;; We activate a chat again on new messages
|
;; We activate a chat again on new messages
|
||||||
:is-active true
|
:is-active true
|
||||||
:timestamp now})
|
:timestamp now})
|
||||||
(add-received-message message)
|
(add-single-received-message message)))
|
||||||
(requests-events/add-request chat-id message-id)
|
|
||||||
(confirm-messages-processed js-obj)))
|
(defn receive-many
|
||||||
|
[messages {:keys [now] :as cofx}]
|
||||||
|
(let [chat-ids (into #{} (map :chat-id) messages)
|
||||||
|
chat-effects (handlers-macro/merge-effects cofx
|
||||||
|
(fn [chat-id cofx]
|
||||||
|
(chat-model/upsert-chat {:chat-id chat-id
|
||||||
|
:is-active true
|
||||||
|
:timestamp now}
|
||||||
|
cofx))
|
||||||
|
chat-ids)
|
||||||
|
message-effects (handlers-macro/merge-effects chat-effects cofx add-batch-received-message messages)]
|
||||||
|
(handlers-macro/merge-effects message-effects
|
||||||
|
cofx
|
||||||
|
(fn [chat-id cofx]
|
||||||
|
(handlers-macro/merge-fx cofx
|
||||||
|
(re-index-message-groups chat-id)
|
||||||
|
(group-messages chat-id messages)))
|
||||||
|
chat-ids)))
|
||||||
|
|
||||||
(defn system-message [chat-id message-id timestamp content]
|
(defn system-message [chat-id message-id timestamp content]
|
||||||
{:message-id message-id
|
{:message-id message-id
|
||||||
|
@ -214,7 +290,7 @@
|
||||||
(handlers-macro/merge-fx cofx
|
(handlers-macro/merge-fx cofx
|
||||||
(chat-model/upsert-chat {:chat-id chat-id
|
(chat-model/upsert-chat {:chat-id chat-id
|
||||||
:timestamp now})
|
:timestamp now})
|
||||||
(add-message chat-id message-with-id true)
|
(add-single-message message-with-id true)
|
||||||
(send chat-id message-id send-record))))
|
(send chat-id message-id send-record))))
|
||||||
|
|
||||||
(defn send-push-notification [fcm-token status cofx]
|
(defn send-push-notification [fcm-token status cofx]
|
||||||
|
@ -238,9 +314,27 @@
|
||||||
(send chat-id message-id send-record)
|
(send chat-id message-id send-record)
|
||||||
(update-message-status message :sending))))
|
(update-message-status message :sending))))
|
||||||
|
|
||||||
(defn delete-message [chat-id message-id {:keys [db]}]
|
(defn- remove-message-from-group [chat-id {:keys [timestamp message-id]} {:keys [db]}]
|
||||||
{:db (update-in db [:chats chat-id :messages] dissoc message-id)
|
(let [datemark (time/day-relative timestamp)]
|
||||||
:data-store/tx [(messages-store/delete-message-tx message-id)]})
|
{:db (update-in db [:chats chat-id :message-groups]
|
||||||
|
(fn [groups]
|
||||||
|
(let [message-references (get groups datemark)]
|
||||||
|
(if (= 1 (count message-references))
|
||||||
|
;; message removed is the only one in group, remove whole group
|
||||||
|
(dissoc groups datemark)
|
||||||
|
;; remove message from `message-references` list
|
||||||
|
(assoc groups datemark
|
||||||
|
(remove (comp (partial = message-id) :message-id)
|
||||||
|
message-references))))))}))
|
||||||
|
|
||||||
|
(defn delete-message
|
||||||
|
"Deletes chat message, along its occurence in all references, like `:message-groups`"
|
||||||
|
[chat-id message-id {:keys [db] :as cofx}]
|
||||||
|
(handlers-macro/merge-fx
|
||||||
|
cofx
|
||||||
|
{:db (update-in db [:chats chat-id :messages] dissoc message-id)
|
||||||
|
:data-store/tx [(messages-store/delete-message-tx message-id)]}
|
||||||
|
(remove-message-from-group chat-id (get-in db [:chats chat-id :messages message-id]))))
|
||||||
|
|
||||||
(defn send-message [{:keys [db now random-id] :as cofx} {:keys [chat-id] :as params}]
|
(defn send-message [{:keys [db now random-id] :as cofx} {:keys [chat-id] :as params}]
|
||||||
(upsert-and-send (prepare-plain-message chat-id params (get-in db [:chats chat-id]) now) cofx))
|
(upsert-and-send (prepare-plain-message chat-id params (get-in db [:chats chat-id]) now) cofx))
|
||||||
|
|
|
@ -100,7 +100,7 @@
|
||||||
message-view]]]))
|
message-view]]]))
|
||||||
|
|
||||||
(defview messages-view [group-chat]
|
(defview messages-view [group-chat]
|
||||||
(letsubs [messages [:get-current-chat-messages]
|
(letsubs [messages [:get-current-chat-messages-stream]
|
||||||
chat-id [:get-current-chat-id]
|
chat-id [:get-current-chat-id]
|
||||||
current-public-key [:get-current-public-key]]
|
current-public-key [:get-current-public-key]]
|
||||||
{:component-did-mount #(re-frame/dispatch [:set-chat-ui-props {:messages-focused? true
|
{:component-did-mount #(re-frame/dispatch [:set-chat-ui-props {:messages-focused? true
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
(s/def :chat/public-group-topic (s/nilable string?))
|
(s/def :chat/public-group-topic (s/nilable string?))
|
||||||
(s/def :chat/public-group-topic-error (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
|
(s/def :chat/messages (s/nilable map?)) ; messages indexed by message-id
|
||||||
|
(s/def :chat/message-groups (s/nilable map?)) ; grouped/sorted messages
|
||||||
(s/def :chat/not-loaded-message-ids (s/nilable set?)) ; set of message-ids not yet fully loaded from persisted state
|
(s/def :chat/not-loaded-message-ids (s/nilable set?)) ; set of message-ids not yet fully loaded from persisted state
|
||||||
(s/def :chat/last-clock-value (s/nilable number?)) ; last logical clock value of messages in chat
|
(s/def :chat/last-clock-value (s/nilable number?)) ; last logical clock value of messages in chat
|
||||||
(s/def :chat/loaded-chats (s/nilable seq?))
|
(s/def :chat/loaded-chats (s/nilable seq?))
|
||||||
|
|
|
@ -84,67 +84,37 @@
|
||||||
(fn [{:keys [messages]} [_ message-id]]
|
(fn [{:keys [messages]} [_ message-id]]
|
||||||
(get messages message-id)))
|
(get messages message-id)))
|
||||||
|
|
||||||
(defn- intersperse-datemark
|
(reg-sub
|
||||||
"Reduce step which expects the input list of messages to be sorted by clock value.
|
:get-current-chat-messages
|
||||||
It makes best effort to group them by day.
|
:<- [:get-current-chat]
|
||||||
We cannot sort them by :timestamp, as that represents the clock of the sender
|
(fn [{:keys [messages]}]
|
||||||
and we have no guarantees on the order.
|
(or messages {})))
|
||||||
|
|
||||||
We naively and arbitrarly group them assuming that out-of-order timestamps
|
(reg-sub
|
||||||
fall in the previous bucket.
|
:get-current-chat-message-groups
|
||||||
|
:<- [:get-current-chat]
|
||||||
|
(fn [{:keys [message-groups]}]
|
||||||
|
(or message-groups {})))
|
||||||
|
|
||||||
A sends M1 to B with timestamp 2000-01-01T00:00:00
|
(defn sort-message-groups
|
||||||
B replies M2 with timestamp 1999-12-31-23:59:59
|
"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))
|
||||||
|
|
||||||
M1 needs to be displayed before M2
|
(defn messages-with-datemarks
|
||||||
|
"Converts message groups into sequence of messages interspersed with datemarks"
|
||||||
so we bucket both in 1999-12-31"
|
[message-groups messages]
|
||||||
[{:keys [acc last-timestamp last-datemark]} {:keys [timestamp datemark] :as msg}]
|
(mapcat (fn [[datemark message-references]]
|
||||||
(cond (empty? acc) ; initial element
|
(into (list {:value datemark
|
||||||
{:last-timestamp timestamp
|
:type :datemark})
|
||||||
:last-datemark datemark
|
(map (fn [{:keys [message-id timestamp-str]}]
|
||||||
:acc (conj acc msg)}
|
(assoc (get messages message-id)
|
||||||
|
:datemark datemark
|
||||||
(and (not= last-datemark datemark) ; not the same day
|
:timestamp-str timestamp-str)))
|
||||||
(< timestamp last-timestamp)) ; not out-of-order
|
message-references))
|
||||||
{:last-timestamp timestamp
|
message-groups))
|
||||||
:last-datemark datemark
|
|
||||||
:acc (conj acc {:value last-datemark ; intersperse datemark message
|
|
||||||
:type :datemark}
|
|
||||||
msg)}
|
|
||||||
:else
|
|
||||||
{:last-timestamp (max timestamp last-timestamp) ; use last datemark
|
|
||||||
:last-datemark last-datemark
|
|
||||||
:acc (conj acc (assoc msg :datemark last-datemark))}))
|
|
||||||
|
|
||||||
(defn sort-messages
|
|
||||||
"Remove hidden messages and sort by clock-value desc, breaking ties by message id"
|
|
||||||
[id->messages]
|
|
||||||
(->> id->messages
|
|
||||||
vals
|
|
||||||
(filter :show?)
|
|
||||||
(sort-by (juxt (comp unchecked-negate :clock-value) :message-id))))
|
|
||||||
|
|
||||||
(defn- add-datemark [{:keys [timestamp] :as msg}]
|
|
||||||
(assoc msg :datemark (time/day-relative timestamp)))
|
|
||||||
|
|
||||||
(defn- add-timestamp [{:keys [timestamp] :as msg}]
|
|
||||||
(assoc msg :timestamp-str (time/timestamp->time timestamp)))
|
|
||||||
|
|
||||||
(defn intersperse-datemarks
|
|
||||||
"Add a datemark in between an ordered seq of messages when two datemarks are not
|
|
||||||
the same. Ignore messages with out-of-order timestamps"
|
|
||||||
[messages]
|
|
||||||
(when (seq messages)
|
|
||||||
(let [messages-with-datemarks (transduce (comp
|
|
||||||
(map add-datemark)
|
|
||||||
(map add-timestamp))
|
|
||||||
(completing intersperse-datemark :acc)
|
|
||||||
{:acc []}
|
|
||||||
messages)]
|
|
||||||
; Append last datemark
|
|
||||||
(conj messages-with-datemarks {:value (:datemark (peek messages-with-datemarks))
|
|
||||||
:type :datemark}))))
|
|
||||||
|
|
||||||
(defn- set-previous-message-info [stream]
|
(defn- set-previous-message-info [stream]
|
||||||
(let [{:keys [display-photo?] :as previous-message} (peek stream)]
|
(let [{:keys [display-photo?] :as previous-message} (peek stream)]
|
||||||
|
@ -212,17 +182,13 @@
|
||||||
:stream))))
|
:stream))))
|
||||||
|
|
||||||
(reg-sub
|
(reg-sub
|
||||||
:get-ordered-chat-messages
|
:get-current-chat-messages-stream
|
||||||
(fn [[_ chat-id]]
|
:<- [:get-current-chat-messages]
|
||||||
(subscribe [:get-chat chat-id]))
|
:<- [:get-current-chat-message-groups]
|
||||||
(fn [{:keys [messages]}]
|
(fn [[messages message-groups]]
|
||||||
(sort-messages messages)))
|
(-> (sort-message-groups message-groups messages)
|
||||||
|
(messages-with-datemarks messages)
|
||||||
(reg-sub
|
messages-stream)))
|
||||||
:get-current-chat-messages
|
|
||||||
:<- [:get-current-chat]
|
|
||||||
(fn [{:keys [messages]}]
|
|
||||||
(-> messages sort-messages intersperse-datemarks messages-stream)))
|
|
||||||
|
|
||||||
(reg-sub
|
(reg-sub
|
||||||
:get-commands-for-chat
|
:get-commands-for-chat
|
||||||
|
@ -384,8 +350,14 @@
|
||||||
(reg-sub
|
(reg-sub
|
||||||
:get-last-message
|
:get-last-message
|
||||||
(fn [[_ chat-id]]
|
(fn [[_ chat-id]]
|
||||||
(subscribe [:get-ordered-chat-messages chat-id]))
|
(subscribe [:get-chat chat-id]))
|
||||||
first)
|
(fn [{:keys [messages message-groups]}]
|
||||||
|
(->> (sort-message-groups message-groups messages)
|
||||||
|
first
|
||||||
|
second
|
||||||
|
last
|
||||||
|
:message-id
|
||||||
|
(get messages))))
|
||||||
|
|
||||||
(reg-sub
|
(reg-sub
|
||||||
:chat-animations
|
:chat-animations
|
||||||
|
|
|
@ -99,8 +99,9 @@
|
||||||
{:chat-received-message/add-fx
|
{:chat-received-message/add-fx
|
||||||
[(assoc (into {} this)
|
[(assoc (into {} this)
|
||||||
:message-id (transport.utils/message-id this)
|
:message-id (transport.utils/message-id this)
|
||||||
:chat-id chat-id
|
:show? true
|
||||||
:from signature)]}))
|
:chat-id chat-id
|
||||||
|
:from signature)]}))
|
||||||
|
|
||||||
(defrecord MessagesSeen [message-ids]
|
(defrecord MessagesSeen [message-ids]
|
||||||
message/StatusMessage
|
message/StatusMessage
|
||||||
|
|
|
@ -232,6 +232,7 @@
|
||||||
:chat/public-group-topic
|
:chat/public-group-topic
|
||||||
:chat/public-group-topic-error
|
:chat/public-group-topic-error
|
||||||
:chat/messages
|
:chat/messages
|
||||||
|
:chat/message-groups
|
||||||
:chat/not-loaded-message-ids
|
:chat/not-loaded-message-ids
|
||||||
:chat/last-clock-value
|
:chat/last-clock-value
|
||||||
:chat/loaded-chats
|
:chat/loaded-chats
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
(let [_ (when (or (not @chat-id*) (not= @chat-id* chat-id))
|
(let [_ (when (or (not @chat-id*) (not= @chat-id* chat-id))
|
||||||
(reset! chat-id* chat-id)
|
(reset! chat-id* chat-id)
|
||||||
(js/setTimeout #(when scroll-ref (.scrollToEnd @scroll-ref)) 400))
|
(js/setTimeout #(when scroll-ref (.scrollToEnd @scroll-ref)) 400))
|
||||||
messages (re-frame/subscribe [:get-current-chat-messages])
|
messages (re-frame/subscribe [:get-current-chat-messages-stream])
|
||||||
current-public-key (re-frame/subscribe [:get-current-public-key])]
|
current-public-key (re-frame/subscribe [:get-current-public-key])]
|
||||||
[react/view {:style {:flex 1 :background-color :white :margin-horizontal 16}}
|
[react/view {:style {:flex 1 :background-color :white :margin-horizontal 16}}
|
||||||
[react/scroll-view {:scrollEventThrottle 16
|
[react/scroll-view {:scrollEventThrottle 16
|
||||||
|
|
|
@ -61,7 +61,9 @@
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:clear-history
|
:clear-history
|
||||||
(fn [{{:keys [current-chat-id] :as db} :db} _]
|
(fn [{{:keys [current-chat-id] :as db} :db} _]
|
||||||
{:db (assoc-in db [:chats current-chat-id :messages] {})
|
{:db (-> db
|
||||||
|
(assoc-in [:chats current-chat-id :messages] {})
|
||||||
|
(assoc-in [:chats current-chat-id :message-groups] {}))
|
||||||
:data-store/tx [(messages-store/hide-messages-tx current-chat-id)]}))
|
:data-store/tx [(messages-store/hide-messages-tx current-chat-id)]}))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
|
|
|
@ -25,11 +25,15 @@
|
||||||
(select-keys new-fx mergable-keys)))
|
(select-keys new-fx mergable-keys)))
|
||||||
{:merging-fx-with-common-keys common-keys}))))
|
{:merging-fx-with-common-keys common-keys}))))
|
||||||
|
|
||||||
(defn merge-effects [{:keys [db] :as initial-cofx} handler args]
|
(defn merge-effects
|
||||||
(reduce (fn [fx arg]
|
([{:keys [db] :as cofx} handler args]
|
||||||
(let [temp-cofx (update-db initial-cofx fx)]
|
(merge-effects {:db db} cofx handler args))
|
||||||
(safe-merge
|
([initial-fx {:keys [db] :as cofx} handler args]
|
||||||
fx
|
(reduce (fn [fx arg]
|
||||||
(handler arg temp-cofx))))
|
(let [temp-cofx (update-db cofx fx)]
|
||||||
{:db db}
|
(safe-merge
|
||||||
args))
|
fx
|
||||||
|
(handler arg temp-cofx))))
|
||||||
|
(or initial-fx
|
||||||
|
{:db db})
|
||||||
|
args)))
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
(ns status-im.test.chat.models.message
|
(ns status-im.test.chat.models.message
|
||||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||||
[status-im.transport.message.v1.protocol :as protocol]
|
[status-im.transport.message.v1.protocol :as protocol]
|
||||||
[status-im.chat.models.message :as message]))
|
[status-im.chat.models.message :as message]
|
||||||
|
[status-im.utils.datetime :as time]))
|
||||||
|
|
||||||
(deftest add-to-chat?
|
(deftest add-to-chat?
|
||||||
(testing "it returns true when it's not in loaded message"
|
(testing "it returns true when it's not in loaded message"
|
||||||
|
@ -31,9 +32,11 @@
|
||||||
:account/account {:public-key "a"}
|
:account/account {:public-key "a"}
|
||||||
:current-chat-id "chat-id"
|
:current-chat-id "chat-id"
|
||||||
:view-id :chat}}
|
:view-id :chat}}
|
||||||
message {:chat-id "chat-id"
|
message {:chat-id "chat-id"
|
||||||
:from "a"
|
:from "a"
|
||||||
:message-id "1"}
|
:message-id "1"
|
||||||
|
:clock-value 0
|
||||||
|
:timestamp 0}
|
||||||
extract-seen (comp :payload :message first :shh/post)]
|
extract-seen (comp :payload :message first :shh/post)]
|
||||||
(testing "it send a seen message when the chat is 1-to-1 and is open"
|
(testing "it send a seen message when the chat is 1-to-1 and is open"
|
||||||
(is (instance? protocol/MessagesSeen
|
(is (instance? protocol/MessagesSeen
|
||||||
|
@ -59,3 +62,72 @@
|
||||||
(message/receive
|
(message/receive
|
||||||
message
|
message
|
||||||
(assoc-in db [:db :account/account :public-key] nil))))))))
|
(assoc-in db [:db :account/account :public-key] nil))))))))
|
||||||
|
|
||||||
|
(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
|
||||||
|
:content "a"
|
||||||
|
:clock-value 0
|
||||||
|
:timestamp (- timestamp 1)}
|
||||||
|
1 {:message-id 1
|
||||||
|
:content "b"
|
||||||
|
:clock-value 1
|
||||||
|
:timestamp timestamp}}
|
||||||
|
:message-groups {"datetime-today" '({:message-id 1}
|
||||||
|
{:message-id 0})}}}}}
|
||||||
|
cofx2 {:db {:chats {"chat-id" {:messages {0 {:message-id 0
|
||||||
|
:content "a"
|
||||||
|
:clock-value 0
|
||||||
|
:timestamp timestamp}}
|
||||||
|
:message-groups {"datetime-today" '({:message-id 0})}}}}}
|
||||||
|
fx1 (message/delete-message "chat-id" 1 cofx1)
|
||||||
|
fx2 (message/delete-message "chat-id" 0 cofx2)]
|
||||||
|
(testing "Deleting message deletes it along with all references"
|
||||||
|
(is (= '(0)
|
||||||
|
(keys (get-in fx1 [:db :chats "chat-id" :messages]))))
|
||||||
|
(is (= {"datetime-today" '({:message-id 0})}
|
||||||
|
(get-in fx1 [:db :chats "chat-id" :message-groups])))
|
||||||
|
(is (= {}
|
||||||
|
(get-in fx2 [:db :chats "chat-id" :messages])))
|
||||||
|
(is (= {}
|
||||||
|
(get-in fx2 [:db :chats "chat-id" :message-groups]))))))
|
||||||
|
|
|
@ -3,57 +3,6 @@
|
||||||
[status-im.constants :as const]
|
[status-im.constants :as const]
|
||||||
[status-im.chat.subs :as s]))
|
[status-im.chat.subs :as s]))
|
||||||
|
|
||||||
(deftest test-message-datemark-groups
|
|
||||||
(testing "it orders a map of messages by clock-values desc, breaking ties by message-id asc and removing hidden messages"
|
|
||||||
(let [message-1 {:show? true
|
|
||||||
:message-id "doesn't matter 1"
|
|
||||||
:clock-value 1}
|
|
||||||
message-2 {:show? true
|
|
||||||
:message-id "doesn't matter 2"
|
|
||||||
:clock-value 2}
|
|
||||||
message-3 {:show? true
|
|
||||||
:message-id "does matter 2"
|
|
||||||
:clock-value 3}
|
|
||||||
message-4 {:show? true
|
|
||||||
:message-id "does matter 1"
|
|
||||||
:clock-value 3}
|
|
||||||
hidden-message {:show? false
|
|
||||||
:clock-value 1}
|
|
||||||
unordered-messages (->> [message-1
|
|
||||||
message-2
|
|
||||||
message-3
|
|
||||||
message-4
|
|
||||||
hidden-message]
|
|
||||||
(map (juxt :message-id identity))
|
|
||||||
shuffle ; clojure maps are sorted for n <= 32
|
|
||||||
(into {}))]
|
|
||||||
(is (= [message-4
|
|
||||||
message-3
|
|
||||||
message-2
|
|
||||||
message-1] (s/sort-messages unordered-messages))))))
|
|
||||||
|
|
||||||
(deftest intersperse-datemarks
|
|
||||||
(testing "it mantains the order even when timestamps are across days"
|
|
||||||
(let [message-1 {:timestamp 946641600000} ; 1999}
|
|
||||||
message-2 {:timestamp 946728000000} ; 2000 this will displayed in 1999
|
|
||||||
message-3 {:timestamp 946641600000} ; 1999
|
|
||||||
message-4 {:timestamp 946728000000} ; 2000
|
|
||||||
ordered-messages [message-4
|
|
||||||
message-3
|
|
||||||
message-2
|
|
||||||
message-1]
|
|
||||||
[m1 d1 m2 m3 m4 d2] (s/intersperse-datemarks ordered-messages)]
|
|
||||||
(is (= "Jan 1, 2000"
|
|
||||||
(:datemark m1)))
|
|
||||||
(is (= {:type :datemark
|
|
||||||
:value "Jan 1, 2000"} d1))
|
|
||||||
(is (= "Dec 31, 1999"
|
|
||||||
(:datemark m2)
|
|
||||||
(:datemark m3)
|
|
||||||
(:datemark m4)))
|
|
||||||
(is (= {:type :datemark
|
|
||||||
:value "Dec 31, 1999"} d2)))))
|
|
||||||
|
|
||||||
(deftest message-stream-tests
|
(deftest message-stream-tests
|
||||||
(testing "messages with no interspersed datemarks"
|
(testing "messages with no interspersed datemarks"
|
||||||
(let [m1 {:from "1"
|
(let [m1 {:from "1"
|
||||||
|
|
Loading…
Reference in New Issue