[slow sign in] Add unviewed messages counter to chat entity.

Before we fetched ALL user-statuses with `status=received` (which means that
a message hasn't been seen), iterated them, grouped by chat and then stored
`message-ids` of these `user-statuses` in chat's `:unviewed-messages` key.

This commit introduces :unviewed-messages-count field in chat entity.
That means that there is no need to iterate `user-statuses` in order to count
a total number of unviewed messages, it is always stored along with chat.
In the rest of it, the difference is only that chat's db record should be
updated each time when unviewed messages are seen.
This commit is contained in:
Roman Volosovskyi 2018-11-20 12:34:08 +02:00
parent 6f08a9fe7f
commit 5d5847e4b9
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
14 changed files with 215 additions and 131 deletions

View File

@ -67,12 +67,14 @@
"Adds new public group chat to db & realm"
[cofx topic]
(upsert-chat cofx
{:chat-id topic
:is-active true
:name topic
:group-chat true
:contacts #{}
:public? true}))
{:chat-id topic
:is-active true
:name topic
:group-chat true
:contacts #{}
:public? true
:unviewed-messages-count 0
:loaded-unviewed-messages-ids #{}}))
(fx/defn add-group-chat
"Adds new private group chat to db & realm"
@ -100,8 +102,7 @@
{:db (update-in db [:chats chat-id] merge
{:messages empty-message-map
:message-groups {}
:unviewed-messages #{}
:not-loaded-message-ids #{}
:unviewed-messages-count 0
:deleted-at-clock-value last-message-clock-value})
:data-store/tx [(chats-store/clear-history-tx chat-id last-message-clock-value)
(messages-store/delete-messages-tx chat-id)]}))
@ -135,7 +136,7 @@
(protocol/send (protocol/map->MessagesSeen {:message-ids message-ids}) chat-id cofx)))
(defn- unread-messages-number [chats]
(apply + (map (comp count :unviewed-messages) chats)))
(apply + (map :unviewed-messages-count chats)))
(fx/defn update-dock-badge-label
[cofx]
@ -151,33 +152,52 @@
:else nil)]
{:set-dock-badge-label label}))
(defn subtract-seen-messages
[old-count new-seen-messages-ids]
(max 0 (- old-count (count new-seen-messages-ids))))
(fx/defn update-chats-unviewed-messages-count
[{:keys [db] :as cofx} {:keys [chat-id new-loaded-unviewed-messages-ids]}]
(let [{:keys [loaded-unviewed-messages-ids unviewed-messages-count]}
(get-in db [:chats chat-id])
unviewed-messages-ids (if (seq new-loaded-unviewed-messages-ids)
new-loaded-unviewed-messages-ids
loaded-unviewed-messages-ids)]
(upsert-chat
cofx
{:chat-id chat-id
:unviewed-messages-count (subtract-seen-messages
unviewed-messages-count
unviewed-messages-ids)
:loaded-unviewed-messages-ids #{}})))
;; TODO (janherich) - ressurect `constants/system` messages for group chats in the future
(fx/defn mark-messages-seen
"Marks all unviewed loaded messages as seen in particular chat"
[{:keys [db] :as cofx} chat-id]
(when-let [all-unviewed-ids (seq (get-in db [:chats chat-id :unviewed-messages]))]
(let [me (accounts.db/current-public-key cofx)
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)
(fx/merge 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)
(when platform/desktop?
(update-dock-badge-label)))))))
(let [public-key (accounts.db/current-public-key cofx)
loaded-unviewed-ids (get-in db [:chats chat-id :loaded-unviewed-messages-ids])
updated-statuses (map (fn [message-id]
{:chat-id chat-id
:message-id message-id
:status-id (str chat-id "-" message-id)
:public-key public-key
:status :seen})
loaded-unviewed-ids)]
(when (seq loaded-unviewed-ids)
(fx/merge cofx
{:db (reduce (fn [acc {:keys [message-id status]}]
(assoc-in acc [:chats chat-id :message-statuses
message-id public-key :status]
status))
db
updated-statuses)
:data-store/tx [(user-statuses-store/save-statuses-tx updated-statuses)]}
(update-chats-unviewed-messages-count {:chat-id chat-id})
(send-messages-seen chat-id loaded-unviewed-ids)
(when platform/desktop?
(update-dock-badge-label))))))
(fx/defn preload-chat-data
"Takes chat-id and coeffects map, returns effects necessary when navigating to chat"

View File

@ -52,28 +52,39 @@
(filter #(not (contains? message-id->messages %))))
(vals message-id->messages)))
(defn get-unviewed-messages-ids
[statuses public-key]
(keep
(fn [[message-id statuses]]
(let [{:keys [status]}
(get statuses public-key)]
(when (= (keyword status) :received)
message-id)))
statuses))
(fx/defn load-chats-messages
[{:keys [db get-stored-messages get-stored-user-statuses
get-referenced-messages get-stored-unviewed-messages]
[{:keys [db get-stored-messages get-stored-user-statuses get-referenced-messages]
:as cofx}]
(let [chats (:chats db)]
(let [chats (:chats db)
public-key (accounts.db/current-public-key cofx)]
(fx/merge
cofx
{:db (assoc
db :chats
(reduce
(fn [chats chat-id]
(let [stored-unviewed-messages (get-stored-unviewed-messages (accounts.db/current-public-key cofx))
chat-messages (index-messages (get-stored-messages chat-id))
message-ids (keys chat-messages)]
(let [chat-messages (index-messages (get-stored-messages chat-id))
message-ids (keys chat-messages)
statuses (get-stored-user-statuses chat-id message-ids)
unviewed-messages-ids (get-unviewed-messages-ids statuses public-key)]
(update
chats
chat-id
assoc
:messages chat-messages
:message-statuses (get-stored-user-statuses chat-id message-ids)
:unviewed-messages (get stored-unviewed-messages chat-id)
:message-statuses statuses
:loaded-unviewed-messages-ids unviewed-messages-ids
:referenced-messages (into {}
(map (juxt :message-id identity)
(get-referenced-messages
@ -86,8 +97,7 @@
"Initialize all persisted chats on startup"
[{:keys [db default-dapps all-stored-chats] :as cofx}]
(let [chats (reduce (fn [acc {:keys [chat-id] :as chat}]
(assoc acc chat-id
(assoc chat :not-loaded-message-ids #{})))
(assoc acc chat-id chat))
{}
all-stored-chats)]
(fx/merge cofx
@ -130,16 +140,19 @@
referenced-messages (index-messages
(get-referenced-messages (get-referenced-ids indexed-messages)))
new-message-ids (keys indexed-messages)
new-statuses (get-stored-user-statuses current-chat-id new-message-ids)]
new-statuses (get-stored-user-statuses current-chat-id new-message-ids)
public-key (accounts.db/current-public-key cofx)
loaded-unviewed-messages (get-unviewed-messages-ids new-statuses public-key)]
(fx/merge 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))
(update-in [:chats current-chat-id :referenced-messages]
#(into (apply dissoc % new-message-ids) referenced-messages))
(assoc-in [:chats current-chat-id :all-loaded?]
(> constants/default-number-of-messages (count new-messages))))}
(chat-model/update-chats-unviewed-messages-count
{:chat-id current-chat-id
:new-loaded-unviewed-messages-ids loaded-unviewed-messages})
(group-chat-messages current-chat-id new-messages)
(chat-model/mark-messages-seen current-chat-id)))))

View File

@ -125,9 +125,11 @@
(update-in [:chats chat-id :messages] assoc message-id prepared-message)
;; this will increase last-clock-value twice when sending our own messages
(update-in [:chats chat-id :last-clock-value] (partial utils.clocks/receive clock-value)))
(and (not current-chat?)
(not= from current-public-key))
(update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id))
(update-in [:chats chat-id :loaded-unviewed-messages-ids]
(fnil conj #{}) message-id))
:data-store/tx [(messages-store/save-message-tx prepared-message)]}
(when (and platform/desktop?
(not batch?)
@ -164,7 +166,7 @@
(fx/defn add-received-message
[{:keys [db now] :as cofx}
batch?
{:keys [from message-id chat-id content content-type clock-value js-obj] :as raw-message}]
{:keys [from message-id chat-id js-obj] :as raw-message}]
(let [{:keys [web3 current-chat-id view-id]} db
current-public-key (accounts.db/current-public-key cofx)
current-chat? (and (or (= :chat view-id)
@ -200,10 +202,9 @@
(defn- add-to-chat?
[{:keys [db]} {:keys [chat-id clock-value message-id from]}]
(let [{:keys [deleted-at-clock-value messages not-loaded-message-ids]}
(let [{:keys [deleted-at-clock-value messages]}
(get-in db [:chats chat-id])]
(not (or (get messages message-id)
(get not-loaded-message-ids message-id)
(>= deleted-at-clock-value clock-value)
(messages-store/message-exists? message-id)))))
@ -231,15 +232,37 @@
(= (accounts.db/current-public-key cofx) from)) chat-id
(= :user-message message-type) from))
(defn calculate-unviewed-messages-count
[{:keys [db] :as cofx} chat-id messages]
(let [{:keys [current-chat-id view-id]} db
chat-view? (or (= :chat view-id)
(= :chat-modal view-id))
current-public-key (accounts.db/current-public-key cofx)]
(+ (get-in db [:chats chat-id :unviewed-messages-count])
(if (and chat-view? (= current-chat-id chat-id))
0
(count (remove
(fn [{:keys [from]}]
(= from current-public-key))
messages))))))
(fx/defn receive-many
[{:keys [now] :as cofx} messages]
(let [valid-messages (keep #(when-let [chat-id (extract-chat-id cofx %)] (assoc % :chat-id chat-id)) messages)
deduped-messages (filter-messages cofx valid-messages)
chat->message (group-by :chat-id deduped-messages)
chat-ids (keys chat->message)
chats-fx-fns (map #(chat-model/upsert-chat {:chat-id %
:is-active true
:timestamp now})
chats-fx-fns (map (fn [chat-id]
(let [unviewed-messages-count
(calculate-unviewed-messages-count
cofx
chat-id
(get chat->message chat-id))]
(chat-model/upsert-chat
{:chat-id chat-id
:is-active true
:timestamp now
:unviewed-messages-count unviewed-messages-count})))
chat-ids)
messages-fx-fns (map #(add-received-message true %) deduped-messages)
groups-fx-fns (map #(update-group-messages chat->message %) chat-ids)]

View File

@ -16,7 +16,6 @@
(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/message-statuses (s/nilable map?)) ; message/user statuses indexed by two level index
(s/def :chat/not-loaded-message-ids (s/nilable set?)) ; set of message-ids not yet fully loaded from persisted state
(s/def :chat/referenced-messages (s/nilable map?)) ; map of messages indexed by message-id which are not displayed directly, but referenced by other messages
(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?))

View File

@ -209,8 +209,8 @@
:chats/unviewed-messages-count
(fn [[_ chat-id]]
(re-frame/subscribe [:chats/chat chat-id]))
(fn [{:keys [unviewed-messages]}]
(count unviewed-messages)))
(fn [{:keys [unviewed-messages-count]}]
unviewed-messages-count))
(re-frame/reg-sub
:chats/photo-path
@ -237,7 +237,7 @@
:chats/unread-messages-number
:<- [:chats/active-chats]
(fn [chats _]
(apply + (map (comp count :unviewed-messages) (vals chats)))))
(apply + (map :unviewed-messages-count (vals chats)))))
(re-frame/reg-sub
:chats/transaction-confirmed?

View File

@ -42,24 +42,6 @@
(defn- sha3 [s]
(.sha3 dependencies/Web3.prototype s))
(defn- get-unviewed-messages
[public-key]
(-> @core/account-realm
(core/get-by-fields
:user-status
:and {:public-key public-key
:status "received"})
(.reduce (fn [acc msg _ _]
(let [chat-id (aget msg "chat-id")
message-id (aget msg "message-id")]
(update acc chat-id (fnil conj #{}) message-id)))
{})))
(re-frame/reg-cofx
:data-store/get-unviewed-messages
(fn [cofx _]
(assoc cofx :get-stored-unviewed-messages get-unviewed-messages)))
(re-frame/reg-cofx
:data-store/get-referenced-messages
(fn [cofx _]

View File

@ -189,3 +189,37 @@
:public? {:type :bool
:default false}
:tags {:type "string[]"}}})
(def v9 {:name :chat
:primaryKey :chat-id
:properties {:chat-id :string
:name :string
:color {:type :string
:default colors/default-chat-color}
:group-chat {:type :bool
:indexed true}
:is-active :bool
:timestamp :int
:contacts {:type "string[]"}
:admins {:type "string[]"}
:membership-updates {:type :list
:objectType :membership-update}
:removed-at {:type :int
:optional true}
:removed-from-at {:type :int
:optional true}
:deleted-at-clock-value {:type :int
:optional true}
:added-to-at {:type :int
:optional true}
:updated-at {:type :int
:optional true}
:message-overhead {:type :int
:default 0}
:debug? {:type :bool
:default false}
:public? {:type :bool
:default false}
:tags {:type "string[]"}
:unviewed-messages-count {:type :int
:default 0}}})

View File

@ -265,6 +265,19 @@
browser/v8
dapp-permissions/v9])
(def v26 [chat/v9
transport/v7
contact/v3
message/v7
mailserver/v11
mailserver-topic/v1
user-status/v2
membership-update/v1
installation/v2
local-storage/v1
browser/v8
dapp-permissions/v9])
;; put schemas ordered by version
(def schemas [{:schema v1
:schemaVersion 1
@ -341,6 +354,6 @@
{:schema v25
:schemaVersion 25
:migration migrations/v25}
{:schema v25
{:schema v26
:schemaVersion 26
:migration migrations/v26}])

View File

@ -229,4 +229,14 @@
"user-status"
new-status-id)
(.delete new-realm user-status)
(aset user-status "status-id" new-status-id)))))))
(aset user-status "status-id" new-status-id))))))
(let [chats (.objects new-realm "chat")]
(dotimes [i (.-length chats)]
(let [chat (aget chats i)
chat-id (aget chat "chat-id")
user-statuses-count (-> (.objects new-realm "user-status")
(.filtered (str "chat-id=\"" chat-id "\""
" and "
"status = \"received\""))
(.-length))]
(aset chat "unviewed-messages-count" user-statuses-count)))))

View File

@ -87,8 +87,7 @@
(handlers/register-handler-fx
:load-chats-messages
[(re-frame/inject-cofx :data-store/get-unviewed-messages)
(re-frame/inject-cofx :data-store/get-messages)
[(re-frame/inject-cofx :data-store/get-messages)
(re-frame/inject-cofx :data-store/get-referenced-messages)
(re-frame/inject-cofx :data-store/get-user-statuses)]
(fn [cofx]

View File

@ -295,7 +295,6 @@
:chat/messages
:chat/message-groups
:chat/message-statuses
:chat/not-loaded-message-ids
:chat/referenced-messages
:chat/last-clock-value
:chat/loaded-chats

View File

@ -14,19 +14,12 @@
[status-im.utils.utils :as utils]
[status-im.ui.components.react :as components]))
(views/defview unviewed-indicator [chat-id]
(let [unviewed-messages-count (re-frame/subscribe [:chats/unviewed-messages-count chat-id])]
(when (pos? @unviewed-messages-count)
[react/view
[react/text {:font :medium}
@unviewed-messages-count]])))
(views/defview chat-list-item-inner-view [{:keys [chat-id name group-chat color public? public-key] :as chat-item}]
(letsubs [photo-path [:contacts/chat-photo chat-id]
unviewed-messages-count [:chats/unviewed-messages-count chat-id]
chat-name [:chats/chat-name chat-id]
current-chat-id [:chats/current-chat-id]
{:keys [content] :as last-message} [:chats/last-message chat-id]]
(views/letsubs [photo-path [:contacts/chat-photo chat-id]
unviewed-messages-count [:chats/unviewed-messages-count chat-id]
chat-name [:chats/chat-name chat-id]
current-chat-id [:chats/current-chat-id]
{:keys [content] :as last-message} [:chats/last-message chat-id]]
(let [name (or chat-name
(gfycat/generate-gfy public-key))
[unviewed-messages-label large?] (if (< 9 unviewed-messages-count)

View File

@ -91,12 +91,11 @@
(deftest clear-history-test
(let [chat-id "1"
cofx {:db {:chats {chat-id {:message-groups {:something "a"}
:messages {"1" {:clock-value 1}
"2" {:clock-value 10}
"3" {:clock-value 2}}
:unviewed-messages #{"3"}
:not-loaded-message-ids #{"2" "3"}}}}}]
cofx {:db {:chats {chat-id {:message-groups {:something "a"}
:messages {"1" {:clock-value 1}
"2" {:clock-value 10}
"3" {:clock-value 2}}
:unviewed-messages-count 1}}}}]
(testing "it deletes all the messages"
(let [actual (chat/clear-history cofx chat-id)]
(is (= {} (get-in actual [:db :chats chat-id :messages])))))
@ -105,10 +104,7 @@
(is (= {} (get-in actual [:db :chats chat-id :message-groups])))))
(testing "it deletes unviewed messages set"
(let [actual (chat/clear-history cofx chat-id)]
(is (= #{} (get-in actual [:db :chats chat-id :unviewed-messages])))))
(testing "it deletes not loaded message ids set"
(let [actual (chat/clear-history cofx chat-id)]
(is (= #{} (get-in actual [:db :chats chat-id :not-loaded-message-ids])))))
(is (= 0 (get-in actual [:db :chats chat-id :unviewed-messages-count])))))
(testing "it sets a deleted-at-clock-value equal to the last message clock-value"
(let [actual (chat/clear-history cofx chat-id)]
(is (= 10 (get-in actual [:db :chats chat-id :deleted-at-clock-value])))))
@ -202,7 +198,7 @@
{:account/account {:public-key "me"}
:chats {"status" {:public? true
:group-chat true
:unviewed-messages #{"6" "5" "4" "3" "2" "1"}
:loaded-unviewed-messages-ids #{"6" "5" "4"}
:message-statuses {"6" {"me" {:message-id "6"
:chat-id "status"
:public-key "me"
@ -215,12 +211,12 @@
:chat-id "status"
:public-key "me"
:status :received}}}}
"opened" {:unviewed-messages #{}
"opened" {:loaded-unviewed-messages-ids #{}
:message-statuses {"1" {"me" {:message-id "1"
:chat-id "opened"
:public-key "me"
:status :seen}}}}
"1-1" {:unviewed-messages #{"6" "5" "4" "3" "2" "1"}
"1-1" {:loaded-unviewed-messages-ids #{"6" "5" "4"}
:message-statuses {"6" {"me" {:message-id "6"
:chat-id "status"
:public-key "me"
@ -242,9 +238,9 @@
(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])))))
(is (= 2 (count (:data-store/tx fx))))
;; for public chats, no confirmation is sent out
(is (= nil (:shh/post fx)))))
(testing "With empty unviewed set, no effects are produced"
(is (= nil (chat/mark-messages-seen {:db test-db} "opened"))))
@ -257,25 +253,34 @@
(deftest update-dock-badge-label
(testing "When user has unseen private messages"
(is (= {:set-dock-badge-label 3}
(chat/update-dock-badge-label {:db {:chats {"0x0" {:is-active true
:public? false
:unviewed-messages #{1 2 3}}
"status" {:is-active true
:public? true
:unviewed-messages #{1 2}}}}}))))
(chat/update-dock-badge-label
{:db {:chats {"0x0" {:is-active true
:public? false
:unviewed-messages-count 3
:loaded-unviewed-messages-ids #{1 2 3}}
"status" {:is-active true
:public? true
:unviewed-messages-count 2
:loaded-unviewed-messages-ids #{1 2}}}}}))))
(testing "When user has unseen public messages and no unseen private messages"
(is (= {:set-dock-badge-label "•"}
(chat/update-dock-badge-label {:db {:chats {"0x0" {:is-active true
:public? false
:unviewed-messages #{}}
"status" {:is-active true
:public? true
:unviewed-messages #{1 2}}}}}))))
(chat/update-dock-badge-label
{:db {:chats {"0x0" {:is-active true
:public? false
:unviewed-messages-count 0
:loaded-unviewed-messages-ids #{}}
"status" {:is-active true
:public? true
:unviewed-messages-count 2
:loaded-unviewed-messages-ids #{1 2}}}}}))))
(testing "When user has no unseen messages"
(is (= {:set-dock-badge-label nil}
(chat/update-dock-badge-label {:db {:chats {"0x0" {:is-active true
:public? false
:unviewed-messages #{}}
"status" {:is-active true
:public? true
:unviewed-messages #{}}}}})))))
(chat/update-dock-badge-label
{:db {:chats {"0x0" {:is-active true
:public? false
:unviewed-messages-count 0
:loaded-unviewed-messages-ids #{}}
"status" {:is-active true
:public? true
:unviewed-messages-count 0
:loaded-unviewed-messages-ids #{}}}}})))))

View File

@ -17,12 +17,6 @@
:from "a"
:clock-value 1
:chat-id "a"}))))
(testing "it returns false when it's already in the not-loaded-message-ids"
(is (not (message/add-to-chat? {:db {:chats {"a" {:not-loaded-message-ids #{"message-id"}}}}}
{:message-id "message-id"
:from "a"
:clock-value 1
:chat-id "a"}))))
(testing "it returns false when the clock-value is the same as the deleted-clock-value in chat"
(is (not (message/add-to-chat? {:db {:chats {"a" {:deleted-at-clock-value 1}}}}
{:message-id "message-id"