offload chat messages

This commit does a few things:

1) Messages are offloaded from any chat once we go back from the home.
This allows us to ignore any message that is coming in from a chat we
are not currently focused.
2) After 5 seconds of not-scrolling activity, any received message that
is not currently visible will be offloaded to the database.
3) Similarly received messages that are not visible will be offloaded to
the database directly

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2020-02-06 16:17:30 +01:00
parent d281c560dc
commit 4734a4ee04
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
10 changed files with 151 additions and 38 deletions

View File

@ -558,6 +558,7 @@ var TopLevel = {
"version" : function () {}, "version" : function () {},
"vibrate" : function () {}, "vibrate" : function () {},
"View" : function () {}, "View" : function () {},
"viewableItems": function() {},
"FlatList" : function () {}, "FlatList" : function () {},
"warn" : function () {}, "warn" : function () {},
"WebView" : function () {}, "WebView" : function () {},

View File

@ -1,6 +1,7 @@
(ns status-im.chat.models.loading (ns status-im.chat.models.loading
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.ui.screens.chat.state :as chat.state]
[status-im.data-store.chats :as data-store.chats] [status-im.data-store.chats :as data-store.chats]
[status-im.data-store.messages :as data-store.messages] [status-im.data-store.messages :as data-store.messages]
[status-im.transport.filters.core :as filters] [status-im.transport.filters.core :as filters]
@ -10,10 +11,15 @@
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.utils.datetime :as time] [status-im.utils.datetime :as time]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.utils.priority-map :refer [empty-message-map]]
[status-im.chat.models.message-list :as message-list] [status-im.chat.models.message-list :as message-list]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
(defn cursor->clock-value [cursor]
(js/parseInt (.substring cursor 51 64)))
(defn clock-value->cursor [clock-value]
(str "000000000000000000000000000000000000000000000000000" clock-value "0x0000000000000000000000000000000000000000000000000000000000000000"))
(fx/defn update-chats-in-app-db (fx/defn update-chats-in-app-db
{:events [:chats-list/load-success]} {:events [:chats-list/load-success]}
[{:keys [db] :as cofx} new-chats] [{:keys [db] :as cofx} new-chats]
@ -22,7 +28,7 @@
(assoc acc chat-id (assoc acc chat-id
(assoc chat (assoc chat
:messages-initialized? false :messages-initialized? false
:messages empty-message-map))) :messages {})))
{} {}
new-chats) new-chats)
chats (merge old-chats chats)] chats (merge old-chats chats)]
@ -31,29 +37,78 @@
:chats/loading? false)} :chats/loading? false)}
(filters/load-filters)))) (filters/load-filters))))
(fx/defn offload-all-messages
[{:keys [db] :as cofx}]
(when-let [current-chat-id (:current-chat-id db)]
{:db (update-in db [:chats current-chat-id]
assoc
:all-loaded? false
:cursor nil
:messages-initialized? false
:messages {}
:message-list nil)}))
(fx/defn handle-chat-visibility-changed
{:events [:chat.ui/message-visibility-changed]}
[{:keys [db] :as cofx} event]
(let [viewable-items (.-viewableItems event)
last-element (aget viewable-items (dec (.-length viewable-items)))]
(when last-element
(let [last-element-clock-value (:clock-value (.-item last-element))
chat-id (:chat-id (.-item last-element))]
(when (and last-element-clock-value
(get-in db [:chats chat-id :messages-initialized?]))
(let [new-messages (reduce-kv (fn [acc message-id {:keys [clock-value] :as v}]
(if (<= last-element-clock-value clock-value)
(assoc acc message-id v)
acc))
{}
(get-in db [:chats chat-id :messages]))]
{:db (update-in db [:chats chat-id]
assoc
:messages new-messages
:all-loaded? false
:message-list (message-list/add-many nil (vals new-messages))
:cursor (clock-value->cursor last-element-clock-value))}))))))
(fx/defn initialize-chats (fx/defn initialize-chats
"Initialize persisted chats on startup" "Initialize persisted chats on startup"
[cofx] [cofx]
(data-store.chats/fetch-chats-rpc cofx {:on-success (data-store.chats/fetch-chats-rpc cofx {:on-success
#(re-frame/dispatch #(re-frame/dispatch
[:chats-list/load-success %])})) [:chats-list/load-success %])}))
(fx/defn handle-failed-loading-messages
{:events [::failed-loading-messages]}
[{:keys [db]} current-chat-id _ err]
(log/error "failed loading messages" current-chat-id err)
(when current-chat-id
{:db (assoc-in db [:chats current-chat-id :loading-messages?] false)}))
(fx/defn messages-loaded (fx/defn messages-loaded
"Loads more messages for current chat" "Loads more messages for current chat"
{:events [::messages-loaded]} {:events [::messages-loaded]}
[{{:keys [current-chat-id] :as db} :db :as cofx} [{{:keys [current-chat-id] :as db} :db :as cofx}
chat-id chat-id
session-id
{:keys [cursor messages]}] {:keys [cursor messages]}]
(when-not (or (nil? current-chat-id) (when-not (or (nil? current-chat-id)
(not= chat-id current-chat-id)) (not= chat-id current-chat-id)
(and (get-in db [:chats current-chat-id :messages-initialized?])
(not= session-id
(get-in db [:chats current-chat-id :messages-initialized?]))))
(let [already-loaded-messages (get-in db [:chats current-chat-id :messages]) (let [already-loaded-messages (get-in db [:chats current-chat-id :messages])
loaded-unviewed-messages-ids (get-in db [:chats current-chat-id :loaded-unviewed-messages-ids] #{}) loaded-unviewed-messages-ids (get-in db [:chats current-chat-id :loaded-unviewed-messages-ids] #{})
;; We remove those messages that are already loaded, as we might get some duplicates ;; We remove those messages that are already loaded, as we might get some duplicates
{:keys [all-messages {:keys [all-messages
new-messages new-messages
unviewed-message-ids]} (reduce (fn [{:keys [all-messages] :as acc} last-clock-value
{:keys [seen message-id] :as message}] unviewed-message-ids]} (reduce (fn [{:keys [last-clock-value all-messages] :as acc}
{:keys [clock-value seen message-id] :as message}]
(cond-> acc (cond-> acc
(or (nil? last-clock-value)
(> last-clock-value clock-value))
(assoc :last-clock-value clock-value)
(not seen) (not seen)
(update :unviewed-message-ids conj message-id) (update :unviewed-message-ids conj message-id)
@ -68,8 +123,9 @@
messages)] messages)]
(fx/merge cofx (fx/merge cofx
{:db (-> db {:db (-> db
(assoc-in [:chats current-chat-id :cursor-clock-value] (when (seq cursor) (cursor->clock-value cursor)))
(assoc-in [:chats current-chat-id :loaded-unviewed-messages-ids] unviewed-message-ids) (assoc-in [:chats current-chat-id :loaded-unviewed-messages-ids] unviewed-message-ids)
(assoc-in [:chats current-chat-id :messages-initialized?] true) (assoc-in [:chats current-chat-id :loading-messages?] false)
(assoc-in [:chats current-chat-id :messages] all-messages) (assoc-in [:chats current-chat-id :messages] all-messages)
(update-in [:chats current-chat-id :message-list] message-list/add-many new-messages) (update-in [:chats current-chat-id :message-list] message-list/add-many new-messages)
(assoc-in [:chats current-chat-id :cursor] cursor) (assoc-in [:chats current-chat-id :cursor] cursor)
@ -78,11 +134,28 @@
(chat-model/mark-messages-seen current-chat-id))))) (chat-model/mark-messages-seen current-chat-id)))))
(fx/defn load-more-messages (fx/defn load-more-messages
[{:keys [db]}] [{:keys [db] :as cofx}]
(when-let [current-chat-id (:current-chat-id db)] (when-let [current-chat-id (:current-chat-id db)]
(when-not (get-in db [:chats current-chat-id :all-loaded?]) (when-let [session-id (get-in db [:chats current-chat-id :messages-initialized?])]
(let [cursor (get-in db [:chats current-chat-id :cursor])] (when-not (or (get-in db [:chats current-chat-id :all-loaded?])
(data-store.messages/messages-by-chat-id-rpc current-chat-id (get-in db [:chats current-chat-id :loading-messages?]))
cursor (let [cursor (get-in db [:chats current-chat-id :cursor])
constants/default-number-of-messages load-messages-fx (data-store.messages/messages-by-chat-id-rpc current-chat-id
#(re-frame/dispatch [::messages-loaded current-chat-id %])))))) cursor
constants/default-number-of-messages
#(re-frame/dispatch [::messages-loaded current-chat-id session-id %])
#(re-frame/dispatch [::failed-loading-messages current-chat-id session-id %]))]
(fx/merge cofx
load-messages-fx
(mailserver/load-gaps-fx current-chat-id)))))))
(fx/defn load-messages
[{:keys [db now] :as cofx}]
(when-let [current-chat-id (:current-chat-id db)]
(when-not (get-in db [:chats current-chat-id :messages-initialized?])
; reset chat viewable-items state
(chat.state/reset)
(fx/merge cofx
{:db (assoc-in db [:chats current-chat-id :messages-initialized?] now)}
(load-more-messages)))))

View File

@ -13,6 +13,7 @@
[status-im.ethereum.core :as ethereum] [status-im.ethereum.core :as ethereum]
[status-im.mailserver.core :as mailserver] [status-im.mailserver.core :as mailserver]
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.ui.screens.chat.state :as view.state]
[status-im.transport.message.protocol :as protocol] [status-im.transport.message.protocol :as protocol]
[status-im.transport.utils :as transport.utils] [status-im.transport.utils :as transport.utils]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
@ -82,12 +83,6 @@
message-to-be-removed (when replace message-to-be-removed (when replace
(get-in db [:chats chat-id :messages replace])) (get-in db [:chats chat-id :messages replace]))
prepared-message (prepare-message message chat-id current-chat?)] prepared-message (prepare-message message chat-id current-chat?)]
(when (and platform/desktop?
(not= from current-public-key)
(get-in db [:multiaccount :desktop-notifications?])
(< (time/seconds-ago (time/to-date timestamp)) constants/one-earth-day))
(let [{:keys [title body prioritary?]} (build-desktop-notification cofx message)]
(.displayNotification react/desktop-notification title body prioritary?)))
(fx/merge cofx (fx/merge cofx
(when message-to-be-removed (when message-to-be-removed
(hide-message chat-id message-to-be-removed)) (hide-message chat-id message-to-be-removed))
@ -103,29 +98,52 @@
(and (not current-chat?) (and (not current-chat?)
(not= from current-public-key)) (not= from current-public-key))
(update-in [:chats chat-id :loaded-unviewed-messages-ids] (update-in [:chats chat-id :loaded-unviewed-messages-ids]
(fnil conj #{}) message-id))}) (fnil conj #{}) message-id))}))))
(when (and platform/desktop?
(not (system-message? prepared-message)))
(chat-model/update-dock-badge-label)))))
(fx/defn add-received-message (fx/defn add-received-message
[{:keys [db] :as cofx} [{:keys [db] :as cofx}
{:keys [from message-id chat-id content] :as message}] {:keys [from
message-id
chat-id
clock-value
content] :as message}]
(let [{:keys [current-chat-id view-id]} db (let [{:keys [current-chat-id view-id]} db
cursor-clock-value (get-in db [:chats current-chat-id :cursor-clock-value])
current-chat? (and (or (= :chat view-id) current-chat? (and (or (= :chat view-id)
(= :chat-modal view-id)) (= :chat-modal view-id))
(= current-chat-id chat-id))] (= current-chat-id chat-id))]
(fx/merge cofx (when (and current-chat?
(add-message {:message message (or (not cursor-clock-value)
:current-chat? current-chat?})))) (<= cursor-clock-value clock-value)))
;; Not in the current view, offload to db and update cursor if necessary
(if (or (not @view.state/viewable-item)
(not= current-chat-id
(:chat-id @view.state/viewable-item))
(<= (:clock-value @view.state/viewable-item)
clock-value))
(add-message cofx {:message message
:current-chat? current-chat?})
(when (and (< clock-value
cursor-clock-value)
(= current-chat-id
(:chat-id (.-item view.state/viewable-item))))
{:db (assoc-in db [:chats chat-id :cursor] (chat-loading/clock-value->cursor clock-value))})))))
(defn- add-to-chat? (defn- add-to-chat?
[{:keys [db]} {:keys [chat-id clock-value message-id from]}] [{:keys [db]} {:keys [chat-id clock-value message-id from]}]
(let [{:keys [deleted-at-clock-value messages]} (let [{:keys [cursor-clock-value deleted-at-clock-value messages]}
(get-in db [:chats chat-id])] (get-in db [:chats chat-id])]
(not (or (get messages message-id) (not (or (get messages message-id)
(>= deleted-at-clock-value clock-value))))) (>= deleted-at-clock-value clock-value)))))
(fx/defn offload-message-from [{:keys [db] :as cofx} chat-id message-id]
(let [old-messages (get-in db [:chats chat-id :messages])]
(when-let [last-clock-value (get-in old-messages [message-id :clock-value])]
(let [new-messages (select-keys old-messages (for [[k v] old-messages :when (<= last-clock-value (:clock-value v))] k))]
(fx/merge cofx
{:db (assoc-in db [:chats chat-id :messages] new-messages)}
(rebuild-message-list chat-id))))))
(defn extract-chat-id [cofx {:keys [chat-id from message-type]}] (defn extract-chat-id [cofx {:keys [chat-id from message-type]}]
"Validate and return a valid chat-id" "Validate and return a valid chat-id"
(cond (cond

View File

@ -57,12 +57,16 @@
:on-success #(re-frame/dispatch [:messages/system-messages-saved (map <-rpc %)]) :on-success #(re-frame/dispatch [:messages/system-messages-saved (map <-rpc %)])
:on-failure #(log/error "failed to save messages" %)})) :on-failure #(log/error "failed to save messages" %)}))
(defn messages-by-chat-id-rpc [chat-id cursor limit on-success] (defn messages-by-chat-id-rpc [chat-id
cursor
limit
on-success
on-failure]
{::json-rpc/call [{:method "shhext_chatMessages" {::json-rpc/call [{:method "shhext_chatMessages"
:params [chat-id cursor limit] :params [chat-id cursor limit]
:on-success (fn [result] :on-success (fn [result]
(on-success (update result :messages #(map <-rpc %)))) (on-success (update result :messages #(map <-rpc %))))
:on-failure #(log/error "failed to get messages" %)}]}) :on-failure on-failure}]})
(defn mark-seen-rpc [chat-id ids] (defn mark-seen-rpc [chat-id ids]
{::json-rpc/call [{:method "shhext_markMessagesSeen" {::json-rpc/call [{:method "shhext_markMessagesSeen"

View File

@ -494,10 +494,7 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:chat.ui/load-more-messages :chat.ui/load-more-messages
(fn [cofx _] (fn [cofx _]
(let [chat-id (get-in cofx [:db :current-chat-id])] (chat.loading/load-more-messages cofx)))
(fx/merge cofx
(chat.loading/load-more-messages)
(mailserver/load-gaps-fx chat-id)))))
(handlers/register-handler-fx (handlers/register-handler-fx
:chat.ui/start-chat :chat.ui/start-chat

View File

@ -0,0 +1,6 @@
(ns status-im.ui.screens.chat.state)
(defonce viewable-item (atom nil))
(defn reset
(reset! vieweable-item nil))

View File

@ -26,6 +26,8 @@
[status-im.ui.screens.profile.tribute-to-talk.views [status-im.ui.screens.profile.tribute-to-talk.views
:as :as
tribute-to-talk.views] tribute-to-talk.views]
[status-im.ui.screens.chat.state :as state]
[status-im.utils.debounce :as debounce]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.ui.screens.chat.extensions.views :as extensions]) [status-im.ui.screens.chat.extensions.views :as extensions])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
@ -307,6 +309,16 @@
(defonce messages-list-ref (atom nil)) (defonce messages-list-ref (atom nil))
(defn on-viewable-items-changed [e]
(reset! state/viewable-item
(let [element (->> (.-viewableItems e)
reverse
(filter (fn [e]
(= :message (:type (.-item e)))))
first)]
(when element (.-item element))))
(debounce/debounce-and-dispatch [:chat.ui/message-visibility-changed e] 5000))
(defview messages-view (defview messages-view
[{:keys [group-chat chat-id pending-invite-inviter-name contact] :as chat} [{:keys [group-chat chat-id pending-invite-inviter-name contact] :as chat}
modal?] modal?]
@ -314,8 +326,6 @@
current-public-key [:multiaccount/public-key]] current-public-key [:multiaccount/public-key]]
{:component-did-update {:component-did-update
(fn [args] (fn [args]
(when-not (:messages-initialized? (second (.-argv (.-props args))))
(re-frame/dispatch [:chat.ui/load-more-messages]))
(re-frame/dispatch [:chat.ui/set-chat-ui-props (re-frame/dispatch [:chat.ui/set-chat-ui-props
{:messages-focused? true {:messages-focused? true
:input-focused? false}]))} :input-focused? false}]))}
@ -334,6 +344,7 @@
:idx idx :idx idx
:list-ref messages-list-ref}]) :list-ref messages-list-ref}])
:inverted true :inverted true
:onViewableItemsChanged on-viewable-items-changed
:onEndReached #(re-frame/dispatch [:chat.ui/load-more-messages]) :onEndReached #(re-frame/dispatch [:chat.ui/load-more-messages])
:onScrollToIndexFailed #() :onScrollToIndexFailed #()
:keyboardShouldPersistTaps :handled} :keyboardShouldPersistTaps :handled}

View File

@ -9,6 +9,7 @@
status-im.ui.screens.wallet.navigation status-im.ui.screens.wallet.navigation
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.chat.models :as chat] [status-im.chat.models :as chat]
[status-im.chat.models.loading :as chat.loading]
[status-im.hardwallet.core :as hardwallet] [status-im.hardwallet.core :as hardwallet]
[status-im.mailserver.core :as mailserver] [status-im.mailserver.core :as mailserver]
[status-im.multiaccounts.recover.core :as recovery] [status-im.multiaccounts.recover.core :as recovery]
@ -200,6 +201,8 @@
(fx/merge cofx (fx/merge cofx
{:db (assoc db :view-id view-id)} {:db (assoc db :view-id view-id)}
#(case view-id #(case view-id
:chat (chat.loading/load-messages cofx)
:home (chat.loading/offload-all-messages cofx)
:keycard-settings (hardwallet/settings-screen-did-load %) :keycard-settings (hardwallet/settings-screen-did-load %)
:reset-card (hardwallet/reset-card-screen-did-load %) :reset-card (hardwallet/reset-card-screen-did-load %)
:enter-pin-login (hardwallet/enter-pin-screen-did-load %) :enter-pin-login (hardwallet/enter-pin-screen-did-load %)

View File

@ -244,4 +244,4 @@
:onTransitionStart (fn [])})}]]) :onTransitionStart (fn [])})}]])
{:initialRouteName (if (= view-id :intro) {:initialRouteName (if (= view-id :intro)
:intro-stack :intro-stack
:login-stack)}))) :login-stack)})))

View File

@ -158,7 +158,7 @@
(testing "our own message" (testing "our own message"
(is (get-in (message/receive-one cofx own-message) [:db :chats "matching" :messages "1"]))) (is (get-in (message/receive-one cofx own-message) [:db :chats "matching" :messages "1"])))
(testing "a message with non matching chat-id" (testing "a message with non matching chat-id"
(is (get-in (message/receive-one cofx bad-chat-id-message) [:db :chats "not-matching" :messages "1"])))))) (is (not (get-in (message/receive-one cofx bad-chat-id-message) [:db :chats "not-matching" :messages "1"])))))))
(deftest delete-message (deftest delete-message
(with-redefs [time/day-relative (constantly "day-relative") (with-redefs [time/day-relative (constantly "day-relative")