diff --git a/src/status_im/chat/models.cljs b/src/status_im/chat/models.cljs index e54c78d58f..abb320669d 100644 --- a/src/status_im/chat/models.cljs +++ b/src/status_im/chat/models.cljs @@ -3,7 +3,6 @@ [taoensso.timbre :as log] [status-im.multiaccounts.model :as multiaccounts.model] [status-im.transport.filters.core :as transport.filters] - [status-im.contact.core :as contact.core] [status-im.data-store.chats :as chats-store] [status-im.data-store.messages :as messages-store] [status-im.ethereum.json-rpc :as json-rpc] @@ -18,7 +17,9 @@ [status-im.utils.types :as types] [status-im.add-new.db :as new-public-chat.db] [status-im.mailserver.topics :as mailserver.topics] - [status-im.mailserver.constants :as mailserver.constants])) + [status-im.mailserver.constants :as mailserver.constants] + [status-im.chat.models.loading :as loading] + [status-im.ui.screens.chat.state :as chat.state])) (defn chats [] (:chats (types/json->clj (js/require "./chats.js")))) @@ -68,6 +69,12 @@ ([cofx chat-id] (timeline-chat? (get-chat cofx chat-id)))) +(defn profile-chat? + ([chat] + (:profile-public-key chat)) + ([cofx chat-id] + (profile-chat? (get-chat cofx chat-id)))) + (defn set-chat-ui-props "Updates ui-props in active chat by merging provided kvs into them" [{:keys [current-chat-id] :as db} kvs] @@ -165,19 +172,6 @@ [{:keys [db] :as cofx} chat-id on-success] (chats-store/save-chat cofx (get-in db [:chats chat-id]) on-success)) -(fx/defn handle-mark-all-read-successful - {:events [::mark-all-read-successful]} - [{:keys [db] :as cofx} chat-id] - {:db (assoc-in db [:chats chat-id :unviewed-messages-count] 0)}) - -(fx/defn handle-mark-all-read - {:events [:chat.ui/mark-all-read-pressed - :chat.ui/mark-public-all-read]} - [{:keys [db] :as cofx} chat-id] - {::json-rpc/call [{:method (json-rpc/call-ext-method "markAllRead") - :params [chat-id] - :on-success #(re-frame/dispatch [::mark-all-read-successful chat-id])}]}) - (fx/defn add-public-chat "Adds new public group chat to db" [cofx topic profile-public-key timeline?] @@ -198,8 +192,7 @@ :contacts #{} :public? true :might-have-join-time-messages? (get-in cofx [:db :multiaccount :use-mailservers?]) - :unviewed-messages-count 0 - :loaded-unviewed-messages-ids #{}} + :unviewed-messages-count 0} nil)) (fx/defn clear-history @@ -232,6 +225,23 @@ (assoc-in [:chats chat-id :is-active] false) (assoc-in [:current-chat-id] nil))}) +(fx/defn offload-messages + {:events [:offload-messages]} + [{:keys [db]} chat-id] + {:db (-> db + (update :messages dissoc chat-id) + (update :message-lists dissoc chat-id) + (update :pagination-info dissoc chat-id))}) + +(fx/defn close-chat + {:events [:close-chat]} + [{:keys [db] :as cofx} chat-id] + (chat.state/reset-visible-item) + (fx/merge cofx + {:db (dissoc db :current-chat-id)} + (offload-messages chat-id) + (navigation/navigate-to-cofx :home {}))) + (fx/defn remove-chat "Removes chat completely from app, producing all necessary effects for that" {:events [:chat.ui/remove-chat]} @@ -240,43 +250,27 @@ (mailserver/remove-gaps chat-id) (mailserver/remove-range chat-id) (deactivate-chat chat-id) + (offload-messages chat-id) (clear-history chat-id true) (transport.filters/stop-listening chat-id) (when (not (= (:view-id db) :home)) (navigation/navigate-to-cofx :home {})))) -(fx/defn offload-all-messages - {:events [::offload-all-messages]} - [{:keys [db] :as cofx}] - (when-let [current-chat-id (:current-chat-id db)] - {:db - (-> db - (dissoc :loaded-chat-id) - (update :messages dissoc current-chat-id) - (update :message-lists dissoc current-chat-id) - (update :pagination-info dissoc current-chat-id))})) - (fx/defn preload-chat-data "Takes chat-id and coeffects map, returns effects necessary when navigating to chat" + {:events [:chat.ui/preload-chat-data]} [{:keys [db] :as cofx} chat-id] - (let [old-current-chat-id (:current-chat-id db)] - (fx/merge cofx - {:dispatch [:load-messages]} - (when-not (= old-current-chat-id chat-id) - (offload-all-messages)) - (fn [{:keys [db]}] - {:db (assoc db :current-chat-id chat-id)}) - ;; Group chat don't need this to load as all the loading of topics - ;; happens on membership changes - (when-not (or (group-chat? cofx chat-id) (timeline-chat? cofx chat-id)) - (transport.filters/load-chat chat-id))))) + (fx/merge cofx + (when-not (or (group-chat? cofx chat-id) (timeline-chat? cofx chat-id)) + (transport.filters/load-chat chat-id)) + (loading/load-messages chat-id))) (fx/defn navigate-to-chat "Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data" {:events [:chat.ui/navigate-to-chat]} [{db :db :as cofx} chat-id] (fx/merge cofx - {:db (assoc db :inactive-chat-id chat-id)} + {:db (assoc db :current-chat-id chat-id)} (preload-chat-data chat-id) (navigation/navigate-to-cofx :chat-stack {:screen :chat}))) @@ -287,17 +281,18 @@ ;; don't allow to open chat with yourself (when (not= (multiaccounts.model/current-public-key cofx) chat-id) (fx/merge cofx + {:dispatch [:chat.ui/navigate-to-chat chat-id]} (upsert-chat {:chat-id chat-id :is-active true} nil) - (transport.filters/load-chat chat-id) - (navigate-to-chat chat-id)))) - -(def timeline-chat-id "@timeline70bd746ddcc12beb96b2c9d572d0784ab137ffc774f5383e50585a932080b57cca0484b259e61cecbaa33a4c98a300a") + (transport.filters/load-chat chat-id)))) (defn profile-chat-topic [public-key] (str "@" public-key)) +(defn my-profile-chat-topic [db] + (profile-chat-topic (get-in db [:multiaccount :public-key]))) + (fx/defn start-public-chat "Starts a new public chat" {:events [:chat.ui/start-public-chat]} @@ -310,7 +305,7 @@ (add-public-chat topic profile-public-key false) (transport.filters/load-chat topic) #(when-not dont-navigate? - (navigate-to-chat % topic)))) + {:dispatch [:chat.ui/navigate-to-chat topic]}))) {:utils/show-popup {:title (i18n/label :t/cant-open-public-chat) :content (i18n/label :t/invalid-public-chat-topic)}})) @@ -327,8 +322,8 @@ (fx/defn start-timeline-chat "Starts a new timeline chat" [cofx] - (when-not (active-chat? cofx timeline-chat-id) - (add-public-chat cofx timeline-chat-id nil true))) + (when-not (active-chat? cofx constants/timeline-chat-id) + (add-public-chat cofx constants/timeline-chat-id nil true))) (fx/defn disable-chat-cooldown "Turns off chat cooldown (protection against message spamming)" @@ -344,17 +339,6 @@ (i18n/label :cooldown/warning-message) #()))) -(fx/defn show-profile-without-adding-contact - {:events [:chat.ui/show-profile-without-adding-contact]} - [{:keys [db] :as cofx} identity] - (let [my-public-key (get-in db [:multiaccount :public-key])] - (if (= my-public-key identity) - (navigation/navigate-to-cofx cofx :profile-stack {:screen :my-profile}) - (fx/merge - cofx - {:db (assoc db :contacts/identity identity)} - (navigation/navigate-to-cofx :profile nil))))) - (fx/defn mute-chat-failed {:events [::mute-chat-failed]} [{:keys [db] :as cofx} chat-id muted? error] @@ -380,10 +364,16 @@ (fx/defn show-profile {:events [:chat.ui/show-profile]} - [cofx identity] - (fx/merge (assoc-in cofx [:db :contacts/identity] identity) - (contact.core/create-contact identity) - (navigation/navigate-to-cofx :profile nil))) + [{:keys [db] :as cofx} identity] + (let [my-public-key (get-in db [:multiaccount :public-key])] + (if (= my-public-key identity) + (navigation/navigate-to-cofx cofx :profile-stack {:screen :my-profile}) + (fx/merge + cofx + {:db (assoc db :contacts/identity identity) + :dispatch [:chat.ui/preload-chat-data (profile-chat-topic identity)]} + (start-profile-chat identity) + (navigation/navigate-to-cofx :profile nil))))) (fx/defn clear-history-pressed {:events [:chat.ui/clear-history-pressed]} @@ -398,13 +388,12 @@ (fx/defn chat-ui-fill-gaps {:events [:chat.ui/fill-gaps]} - [{:keys [db] :as cofx} gap-ids] - (let [chat-id (:current-chat-id db) - topics (mailserver.topics/topics-for-current-chat db) - gaps (keep - (fn [id] - (get-in db [:mailserver/gaps chat-id id])) - gap-ids)] + [{:keys [db] :as cofx} gap-ids chat-id] + (let [topics (mailserver.topics/topics-for-chat db chat-id) + gaps (keep + (fn [id] + (get-in db [:mailserver/gaps chat-id id])) + gap-ids)] (mailserver/fill-the-gap cofx {:gaps gaps @@ -413,16 +402,14 @@ (fx/defn chat-ui-fetch-more {:events [:chat.ui/fetch-more]} - [{:keys [db] :as cofx}] - (let [chat-id (:current-chat-id db) - - {:keys [lowest-request-from]} + [{:keys [db] :as cofx} chat-id] + (let [{:keys [lowest-request-from]} (get-in db [:mailserver/ranges chat-id]) - topics (mailserver.topics/topics-for-current-chat db) - gaps [{:id :first-gap - :to lowest-request-from - :from (- lowest-request-from mailserver.constants/one-day)}]] + topics (mailserver.topics/topics-for-chat db chat-id) + gaps [{:id :first-gap + :to lowest-request-from + :from (- lowest-request-from mailserver.constants/one-day)}]] (mailserver/fill-the-gap cofx {:gaps gaps diff --git a/src/status_im/chat/models/images.cljs b/src/status_im/chat/models/images.cljs index 9a5a14e3bf..576f00907c 100644 --- a/src/status_im/chat/models/images.cljs +++ b/src/status_im/chat/models/images.cljs @@ -9,7 +9,8 @@ [status-im.utils.image-processing :as image-processing] [taoensso.timbre :as log] [clojure.string :as string] - [status-im.utils.platform :as platform])) + [status-im.utils.platform :as platform] + [status-im.chat.models :as chat])) (def maximum-image-size-px 2000) @@ -55,24 +56,24 @@ (re-frame/reg-fx ::chat-open-image-picker-camera - (fn [] + (fn [current-chat-id] (react/show-image-picker-camera - #(re-frame/dispatch [:chat.ui/image-captured (.-path %)]) {}))) + #(re-frame/dispatch [:chat.ui/image-captured current-chat-id (.-path %)]) {}))) (re-frame/reg-fx ::chat-open-image-picker - (fn [] + (fn [chat-id] (react/show-image-picker (fn [^js images] ;; NOTE(Ferossgp): Because we can't highlight the already selected images inside ;; gallery, we just clean previous state and set all newly picked images (when (and platform/ios? (pos? (count images))) - (re-frame/dispatch [:chat.ui/clear-sending-images])) + (re-frame/dispatch [:chat.ui/clear-sending-images chat-id])) (doseq [^js result (if platform/ios? (take config/max-images-batch images) [images])] (resize-and-call (.-path result) - #(re-frame/dispatch [:chat.ui/image-selected (result->id result) %])))) + #(re-frame/dispatch [:chat.ui/image-selected chat-id (result->id result) %])))) ;; NOTE(Ferossgp): On android you cannot set max limit on images, when a user ;; selects too many images the app crashes. {:media-type "photo" @@ -80,10 +81,10 @@ (re-frame/reg-fx ::image-selected - (fn [uri] + (fn [[uri chat-id]] (resize-and-call uri - #(re-frame/dispatch [:chat.ui/image-selected uri %])))) + #(re-frame/dispatch [:chat.ui/image-selected chat-id uri %])))) (re-frame/reg-fx ::camera-roll-get-photos @@ -97,12 +98,12 @@ (fx/defn image-captured {:events [:chat.ui/image-captured]} - [{:keys [db]} uri] - (let [current-chat-id (:current-chat-id db) + [{:keys [db]} chat-id uri] + (let [current-chat-id (or chat-id (:current-chat-id db)) images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])] (when (and (< (count images) config/max-images-batch) (not (get images uri))) - {::image-selected uri}))) + {::image-selected [uri current-chat-id]}))) (fx/defn camera-roll-get-photos {:events [:chat.ui/camera-roll-get-photos]} @@ -116,20 +117,24 @@ (fx/defn clear-sending-images {:events [:chat.ui/clear-sending-images]} - [{:keys [db]}] - (let [current-chat-id (:current-chat-id db)] - {:db (update-in db [:chat/inputs current-chat-id :metadata] assoc :sending-image {})})) + [{:keys [db]} current-chat-id] + {:db (update-in db [:chat/inputs current-chat-id :metadata] assoc :sending-image {})}) (fx/defn cancel-sending-image {:events [:chat.ui/cancel-sending-image]} - [cofx] - (clear-sending-images cofx)) + [{:keys [db] :as cofx} chat-id] + (let [current-chat-id (or chat-id (:current-chat-id db))] + (clear-sending-images cofx current-chat-id))) + +(fx/defn cancel-sending-image-timeline + {:events [:chat.ui/cancel-sending-image-timeline]} + [{:keys [db] :as cofx}] + (cancel-sending-image cofx (chat/my-profile-chat-topic db))) (fx/defn image-selected {:events [:chat.ui/image-selected]} - [{:keys [db]} original uri] - (let [current-chat-id (:current-chat-id db)] - {:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image original] merge {:uri uri})})) + [{:keys [db]} current-chat-id original uri] + {:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image original] merge {:uri uri})}) (fx/defn image-unselected {:events [:chat.ui/image-unselected]} @@ -139,31 +144,46 @@ (fx/defn chat-open-image-picker {:events [:chat.ui/open-image-picker]} - [{:keys [db]}] - (let [current-chat-id (:current-chat-id db) + [{:keys [db]} chat-id] + (let [current-chat-id (or chat-id (:current-chat-id db)) images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])] (when (< (count images) config/max-images-batch) - {::chat-open-image-picker nil}))) + {::chat-open-image-picker current-chat-id}))) + +(fx/defn chat-open-image-picker-timeline + {:events [:chat.ui/open-image-picker-timeline]} + [{:keys [db] :as cofx}] + (chat-open-image-picker cofx (chat/my-profile-chat-topic db))) (fx/defn chat-show-image-picker-camera {:events [:chat.ui/show-image-picker-camera]} - [{:keys [db]}] - (let [current-chat-id (:current-chat-id db) + [{:keys [db]} chat-id] + (let [current-chat-id (or chat-id (:current-chat-id db)) images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])] (when (< (count images) config/max-images-batch) - {::chat-open-image-picker-camera nil}))) + {::chat-open-image-picker-camera current-chat-id}))) + +(fx/defn chat-show-image-picker-camera-timeline + {:events [:chat.ui/show-image-picker-camera-timeline]} + [{:keys [db] :as cofx}] + (chat-show-image-picker-camera cofx (chat/my-profile-chat-topic db))) (fx/defn camera-roll-pick {:events [:chat.ui/camera-roll-pick]} - [{:keys [db]} uri] - (let [current-chat-id (:current-chat-id db) + [{:keys [db]} uri chat-id] + (let [current-chat-id (or chat-id (:current-chat-id db)) images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])] (if (get-in db [:chats current-chat-id :timeline?]) {:db (assoc-in db [:chat/inputs current-chat-id :metadata :sending-image] {}) - ::image-selected uri} + ::image-selected [uri current-chat-id]} (when (and (< (count images) config/max-images-batch) (not (get images uri))) - {::image-selected uri})))) + {::image-selected [uri current-chat-id]})))) + +(fx/defn camera-roll-pick-timeline + {:events [:chat.ui/camera-roll-pick-timeline]} + [{:keys [db] :as cofx} uri] + (camera-roll-pick cofx uri (chat/my-profile-chat-topic db))) (fx/defn save-image-to-gallery {:events [:chat.ui/save-image-to-gallery]} diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index da5ec7ec59..4df25bd34a 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -28,8 +28,15 @@ "Set input text for current-chat. Takes db and input text and cofx as arguments and returns new fx. Always clear all validation messages." {:events [:chat.ui/set-chat-input-text]} - [{{:keys [current-chat-id] :as db} :db} new-input] - {:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))}) + [{db :db} new-input chat-id] + (let [current-chat-id (or chat-id (:current-chat-id db))] + {:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))})) + +(fx/defn set-timeline-input-text + {:events [:chat.ui/set-timeline-input-text]} + [{db :db} new-input] + {:db (assoc-in db [:chat/inputs (chat/my-profile-chat-topic db) :input-text] + (text->emoji new-input))}) (fx/defn select-mention {:events [:chat.ui/select-mention]} @@ -43,7 +50,7 @@ {:db (-> db (assoc-in [:chats/cursor chat-id] cursor) (assoc-in [:chats/mention-suggestions chat-id] nil))} - (set-chat-input-text new-text) + (set-chat-input-text new-text chat-id) ;; NOTE(rasom): Some keyboards do not react on selection property passed to ;; text input (specifically Samsung keyboard with predictive text set on). ;; In this case, if the user continues typing after the programmatic change, @@ -132,8 +139,8 @@ :ens-name preferred-name}))) (defn build-image-messages - [{{:keys [current-chat-id] :as db} :db} chat-id] - (let [images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])] + [{db :db} chat-id] + (let [images (get-in db [:chat/inputs chat-id :metadata :sending-image])] (mapv (fn [[_ {:keys [uri]}]] {:chat-id chat-id :content-type constants/content-type-image @@ -141,13 +148,12 @@ :text (i18n/label :t/update-to-see-image)}) images))) -(fx/defn clean-input [{:keys [db] :as cofx}] - (let [current-chat-id (:current-chat-id db)] - (fx/merge cofx - {:db (-> db - (assoc-in [:chat/inputs current-chat-id :metadata :sending-image] nil) - (assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil))} - (set-chat-input-text nil)))) +(fx/defn clean-input [{:keys [db] :as cofx} current-chat-id] + (fx/merge cofx + {:db (-> db + (assoc-in [:chat/inputs current-chat-id :metadata :sending-image] nil) + (assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil))} + (set-chat-input-text nil current-chat-id))) (fx/defn send-messages [{:keys [db] :as cofx} input-text current-chat-id] (let [image-messages (build-image-messages cofx current-chat-id) @@ -155,17 +161,23 @@ messages (keep identity (conj image-messages text-message))] (when (seq messages) (fx/merge cofx - (clean-input cofx) + (clean-input cofx (:current-chat-id db)) (process-cooldown) (chat.message/send-messages messages))))) (fx/defn send-my-status-message "when not empty, proceed by sending text message with public key topic" {:events [:profile.ui/send-my-status-message]} - [{{:keys [current-chat-id] :as db} :db :as cofx}] - (let [{:keys [input-text]} (get-in db [:chat/inputs current-chat-id]) - chat-id (chat/profile-chat-topic (get-in db [:multiaccount :public-key]))] - (send-messages cofx input-text chat-id))) + [{db :db :as cofx}] + (let [current-chat-id (chat/my-profile-chat-topic db) + {:keys [input-text]} (get-in db [:chat/inputs current-chat-id]) + image-messages (build-image-messages cofx current-chat-id) + text-message (build-text-message cofx input-text current-chat-id) + messages (keep identity (conj image-messages text-message))] + (when (seq messages) + (fx/merge cofx + (clean-input current-chat-id) + (chat.message/send-messages messages))))) (fx/defn send-audio-message [cofx audio-path duration current-chat-id] diff --git a/src/status_im/chat/models/loading.cljs b/src/status_im/chat/models/loading.cljs index 5c6c4eb6c2..5907e7c133 100644 --- a/src/status_im/chat/models/loading.cljs +++ b/src/status_im/chat/models/loading.cljs @@ -1,27 +1,26 @@ (ns status-im.chat.models.loading (:require [re-frame.core :as re-frame] [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.messages :as data-store.messages] - [status-im.mailserver.core :as mailserver] [status-im.utils.fx :as fx] - [status-im.chat.models.reactions :as reactions] [status-im.chat.models.message-list :as message-list] [taoensso.timbre :as log] - [status-im.chat.models.message-seen :as message-seen] - [status-im.chat.models.mentions :as mentions])) + [status-im.ethereum.json-rpc :as json-rpc] + [clojure.string :as string])) (defn cursor->clock-value [^js cursor] (js/parseInt (.substring cursor 51 64))) (defn clock-value->cursor [clock-value] - (str "000000000000000000000000000000000000000000000000000" clock-value "0x0000000000000000000000000000000000000000000000000000000000000000")) + (str "000000000000000000000000000000000000000000000000000" + clock-value + "0x0000000000000000000000000000000000000000000000000000000000000000")) (fx/defn update-chats-in-app-db {:events [:chats-list/load-success]} - [{:keys [db] :as cofx} new-chats] + [{:keys [db]} new-chats] (let [old-chats (:chats db) chats (reduce (fn [acc {:keys [chat-id] :as chat}] (assoc acc chat-id chat)) @@ -31,35 +30,13 @@ {:db (assoc db :chats chats :chats/loading? false)})) -(fx/defn handle-chat-visibility-changed - {:events [:chat.ui/message-visibility-changed]} - [{:keys [db] :as cofx} ^js event] - (let [^js viewable-items (.-viewableItems event) - ^js 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 [:pagination-info 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 [:messages chat-id]))] - {:db (-> db - (assoc-in [:messages chat-id] new-messages) - (assoc-in [:pagination-info chat-id] {:all-loaded? false - :messages-initialized? true - :cursor (clock-value->cursor last-element-clock-value)}) - (assoc-in [:message-lists chat-id] (message-list/add-many nil (vals new-messages))))})))))) - (fx/defn initialize-chats "Initialize persisted chats on startup" [cofx] (data-store.chats/fetch-chats-rpc cofx {:on-success #(re-frame/dispatch [:chats-list/load-success %])})) + (fx/defn handle-failed-loading-messages {:events [::failed-loading-messages]} [{:keys [db]} current-chat-id _ err] @@ -67,104 +44,88 @@ (when current-chat-id {:db (assoc-in db [:pagination-info current-chat-id :loading-messages?] false)})) +(fx/defn handle-mark-all-read-successful + {:events [::mark-all-read-successful]} + [{:keys [db]} chat-id] + {:db (assoc-in db [:chats chat-id :unviewed-messages-count] 0)}) + +(fx/defn handle-mark-all-read + {:events [:chat.ui/mark-all-read-pressed :chat/mark-all-as-read]} + [_ chat-id] + {::json-rpc/call [{:method (json-rpc/call-ext-method "markAllRead") + :params [chat-id] + :on-success #(re-frame/dispatch [::mark-all-read-successful chat-id])}]}) + (fx/defn messages-loaded "Loads more messages for current chat" {:events [::messages-loaded]} - [{{:keys [current-chat-id] :as db} :db :as cofx} - chat-id - session-id - {:keys [cursor messages]}] - (when-not (or (nil? current-chat-id) - (not= chat-id current-chat-id) - (and (get-in db [:pagination-info current-chat-id :messages-initialized?]) - (not= session-id - (get-in db [:pagination-info current-chat-id :messages-initialized?])))) - (let [already-loaded-messages (get-in db [:messages current-chat-id]) - loaded-unviewed-messages-ids (get-in db [:chats current-chat-id :loaded-unviewed-messages-ids] #{}) - users (get-in db [:chats current-chat-id :users] {}) + [{db :db} chat-id session-id {:keys [cursor messages]}] + (when-not (and (get-in db [:pagination-info chat-id :messages-initialized?]) + (not= session-id + (get-in db [:pagination-info chat-id :messages-initialized?]))) + (let [already-loaded-messages (get-in db [:messages chat-id]) ;; We remove those messages that are already loaded, as we might get some duplicates - {:keys [all-messages - new-messages - last-clock-value - unviewed-message-ids - users]} - (reduce (fn [{:keys [last-clock-value all-messages users] :as acc} - {:keys [clock-value seen message-id alias name identicon from] - :as message}] - (let [nickname (get-in db [:contacts/contacts from :nickname])] - (cond-> acc - (and alias (not= alias "")) - (update :users assoc from - (mentions/add-searchable-phrases - {:alias alias - :name (or name alias) - :identicon identicon - :public-key from - :nickname nickname})) - (or (nil? last-clock-value) - (> last-clock-value clock-value)) - (assoc :last-clock-value clock-value) + {:keys [all-messages new-messages senders]} + (reduce (fn [{:keys [all-messages] :as acc} + {:keys [message-id alias from] + :as message}] + (cond-> acc + (and (not (string/blank? alias)) + (not (get-in db [:chats chat-id :users from]))) + (update :senders assoc from message) - (not seen) - (update :unviewed-message-ids conj message-id) + (nil? (get all-messages message-id)) + (update :new-messages conj message) - (nil? (get all-messages message-id)) - (update :new-messages conj message) + :always + (update :all-messages assoc message-id message))) + {:all-messages already-loaded-messages + :senders {} + :new-messages []} + messages) + current-clock-value (get-in db [:pagination-info chat-id :cursor-clock-value]) + clock-value (when cursor (cursor->clock-value cursor))] + {:dispatch [:chat/add-senders-to-chat-users (vals senders)] + :db (-> db + (update-in [:pagination-info chat-id :cursor-clock-value] + #(if (and (seq cursor) (or (not %) (< clock-value %))) + clock-value + %)) - :always - (update :all-messages assoc message-id message)))) - {:all-messages already-loaded-messages - :unviewed-message-ids loaded-unviewed-messages-ids - :users users - :new-messages []} - messages)] - (fx/merge cofx - {:db (-> db - (assoc-in [:pagination-info 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 :users] users) - (assoc-in [:pagination-info current-chat-id :loading-messages?] false) - (assoc-in [:messages current-chat-id] all-messages) - (update-in [:message-lists current-chat-id] message-list/add-many new-messages) - (assoc-in [:pagination-info current-chat-id :cursor] cursor) - (assoc-in [:pagination-info current-chat-id :all-loaded?] - (empty? cursor)))} - (message-seen/mark-messages-seen current-chat-id))))) + (update-in [:pagination-info chat-id :cursor] + #(if (or (empty? cursor) (not current-clock-value) (< clock-value current-clock-value)) + cursor + %)) + (assoc-in [:pagination-info chat-id :loading-messages?] false) + (assoc-in [:messages chat-id] all-messages) + (update-in [:message-lists chat-id] message-list/add-many new-messages) + (assoc-in [:pagination-info chat-id :all-loaded?] + (empty? cursor)))}))) (fx/defn load-more-messages {:events [:chat.ui/load-more-messages]} - [{:keys [db] :as cofx}] - (when-let [current-chat-id (:current-chat-id db)] - (when-let [session-id (get-in db [:pagination-info current-chat-id :messages-initialized?])] - (when-not (or (get-in db [:pagination-info current-chat-id :all-loaded?]) - (get-in db [:pagination-info current-chat-id :loading-messages?])) - (let [cursor (get-in db [:pagination-info current-chat-id :cursor]) - load-messages-fx (data-store.messages/messages-by-chat-id-rpc - 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 - (reactions/load-more-reactions cursor) - (mailserver/load-gaps-fx current-chat-id))))))) + [{:keys [db]} chat-id first-request] + (when-let [session-id (get-in db [:pagination-info chat-id :messages-initialized?])] + (when (and + (not (get-in db [:pagination-info chat-id :all-loaded?])) + (not (get-in db [:pagination-info chat-id :loading-messages?]))) + (let [cursor (get-in db [:pagination-info chat-id :cursor])] + (when (or first-request cursor) + (merge + {:db (assoc-in db [:pagination-info chat-id :loading-messages?] true)} + {:utils/dispatch-later [{:ms 100 :dispatch [:load-more-reactions cursor chat-id]} + {:ms 100 :dispatch [:load-gaps chat-id]}]} + (data-store.messages/messages-by-chat-id-rpc + chat-id + cursor + constants/default-number-of-messages + #(re-frame/dispatch [::messages-loaded chat-id session-id %]) + #(re-frame/dispatch [::failed-loading-messages chat-id session-id %])))))))) (fx/defn load-messages - {:events [:load-messages]} - [{:keys [db now] :as cofx}] - (when-let [current-chat-id (:current-chat-id db)] - (if-not (get-in db [:pagination-info current-chat-id :messages-initialized?]) - (do - ; reset chat first-not-visible-items state - (chat.state/reset) - (fx/merge cofx - {:db (-> db - ;; We keep track of whether there's a loaded chat - ;; which will be reset only if we hit home - (assoc :loaded-chat-id current-chat-id) - (assoc-in [:pagination-info current-chat-id :messages-initialized?] now))} - (message-seen/mark-messages-seen current-chat-id) - (load-more-messages))) - ;; We mark messages as seen in case we received them while on a different tab - (message-seen/mark-messages-seen cofx current-chat-id)))) + [{:keys [db now] :as cofx} chat-id] + (when-not (get-in db [:pagination-info chat-id :messages-initialized?]) + (fx/merge cofx + {:db (assoc-in db [:pagination-info chat-id :messages-initialized?] now) + :utils/dispatch-later [{:ms 500 :dispatch [:chat.ui/mark-all-read-pressed chat-id]}]} + (load-more-messages chat-id true)))) diff --git a/src/status_im/chat/models/message.cljs b/src/status_im/chat/models/message.cljs index bec7d9c1a9..1e1c79990d 100644 --- a/src/status_im/chat/models/message.cljs +++ b/src/status_im/chat/models/message.cljs @@ -1,214 +1,188 @@ (ns status-im.chat.models.message - (:require [re-frame.core :as re-frame] - [status-im.chat.models :as chat-model] - [status-im.chat.models.loading :as chat-loading] + (:require [status-im.chat.models :as chat-model] [status-im.chat.models.message-list :as message-list] [status-im.constants :as constants] [status-im.data-store.messages :as data-store.messages] [status-im.ethereum.json-rpc :as json-rpc] - [status-im.multiaccounts.model :as multiaccounts.model] [status-im.transport.message.protocol :as protocol] - [status-im.ui.screens.chat.state :as view.state] [status-im.utils.fx :as fx] [taoensso.timbre :as log] [status-im.chat.models.mentions :as mentions] - [clojure.string :as string])) + [clojure.string :as string] + [status-im.contact.db :as contact.db] + [status-im.utils.types :as types] + [status-im.ui.screens.chat.state :as view.state] + [status-im.chat.models.loading :as chat.loading] + [status-im.utils.platform :as platform])) -(defn- prepare-message - [message current-chat?] - (cond-> message - current-chat? - (assoc :seen true))) +(defn- message-loaded? + [db chat-id message-id] + (get-in db [:messages chat-id message-id])) +(defn- earlier-than-deleted-at? + [db chat-id clock-value] + (>= (get-in db [:chats chat-id :deleted-at-clock-value]) clock-value)) + +(defn add-timeline-message [acc chat-id message-id message] + (-> acc + (update-in [:db :messages chat-id] assoc message-id message) + (update-in [:db :message-lists chat-id] message-list/add message))) + +;;TODO this is too expensive, probably we could mark message somehow and just hide it in the UI (fx/defn rebuild-message-list [{:keys [db]} chat-id] {:db (assoc-in db [:message-lists chat-id] (message-list/add-many nil (vals (get-in db [:messages chat-id]))))}) -(fx/defn hidden-message-marked-as-seen - {:events [::hidden-message-marked-as-seen]} - [{:keys [db] :as cofx} chat-id _ hidden-message-count] - (when (= 1 hidden-message-count) - {:db (update-in db [:chats chat-id] - update - :unviewed-messages-count dec)})) -(fx/defn hide-message +(defn hide-message "Hide chat message, rebuild message-list" - [{:keys [db] :as cofx} chat-id {:keys [seen message-id]}] - (fx/merge cofx - {:db (update-in db [:messages chat-id] dissoc message-id)} - (data-store.messages/mark-messages-seen chat-id [message-id] #(re-frame/dispatch [::hidden-message-marked-as-seen %1 %2 %3])) - (rebuild-message-list chat-id))) + [{:keys [db]} chat-id message-id] + ;;TODO this is too expensive, probably we could mark message somehow and just hide it in the UI + (rebuild-message-list {:db (update-in db [:messages chat-id] dissoc message-id)} chat-id)) -(fx/defn add-message - [{:keys [db] :as cofx} - {{:keys [chat-id message-id replace timestamp from] :as message} :message - :keys [seen-by-user?]}] - (let [current-public-key (multiaccounts.model/current-public-key cofx) - message-to-be-removed (when replace - (get-in db [:messages chat-id replace])) - prepared-message (prepare-message message seen-by-user?)] - (fx/merge cofx - (when message-to-be-removed - (hide-message chat-id message-to-be-removed)) - (fn [{:keys [db]}] - {:db (cond-> (-> db - ;; We should not be always adding to the list, as it does not make sense - ;; if the chat has not been initialized, but run into - ;; some troubles disabling it, so next time - (update-in [:messages chat-id] assoc message-id prepared-message) - (update-in [:message-lists chat-id] message-list/add prepared-message)) - (and (not seen-by-user?) - (not= from current-public-key) - (not (get-in db [:chats chat-id :profile-public-key])) - (not (get-in db [:chats chat-id :timeline?]))) - (update-in [:chats chat-id :loaded-unviewed-messages-ids] - (fnil conj #{}) message-id))})))) +(fx/defn join-times-messages-checked + "The key :might-have-join-time-messages? in public chats signals that + the public chat is freshly (re)created and requests for messages to the + mailserver for the topic has not completed yet. Likewise, the key + :join-time-mail-request-id is associated a little bit after, to signal that + the request to mailserver was a success. When request is signalled complete + by mailserver, corresponding event :chat.ui/join-times-messages-checked + dissociates these two fileds via this function, thereby signalling that the + public chat is not fresh anymore." + {:events [:chat/join-times-messages-checked]} + [{:keys [db] :as cofx} chat-ids] + (reduce (fn [acc chat-id] + (cond-> acc + (:might-have-join-time-messages? (chat-model/get-chat cofx chat-id)) + (update :db #(chat-model/dissoc-join-time-fields % chat-id)))) + {:db db} + chat-ids)) -(fx/defn add-sender-to-chat-users - [{:keys [db]} {:keys [chat-id alias name identicon from]}] - (when (and alias (not= alias "")) - (let [nickname (get-in db [:contacts/contacts from :nickname])] - {:db (update-in db [:chats chat-id :users] assoc - from - (mentions/add-searchable-phrases - {:alias alias - :name (or name alias) - :identicon identicon - :public-key from - :nickname nickname}))}))) +(fx/defn add-senders-to-chat-users + {:events [:chat/add-senders-to-chat-users]} + [{:keys [db]} messages] + (reduce (fn [acc {:keys [chat-id alias name identicon from]}] + (update-in acc [:db :chats chat-id :users] assoc + from + (mentions/add-searchable-phrases + {:alias alias + :name (or name alias) + :identicon identicon + :public-key from + :nickname (get-in db [:contacts/contacts from :nickname])}))) + {:db db} + messages)) -(fx/defn add-received-message - [{:keys [db] :as cofx} - {:keys [chat-id clock-value] :as message}] - (let [{:keys [loaded-chat-id view-id current-chat-id]} db - cursor-clock-value (get-in db [:chats current-chat-id :cursor-clock-value]) - current-chat? (= chat-id loaded-chat-id)] - (when current-chat? - (fx/merge - cofx - ;; If we don't have any hidden message or the hidden message is before - ;; this one, we add the message to the UI - (if (or (not @view.state/first-not-visible-item) - (<= (:clock-value @view.state/first-not-visible-item) - clock-value)) - (add-message {:message message - :seen-by-user? (and current-chat? - (= view-id :chat))}) - ;; Not in the current view, set all-loaded to false - ;; and offload to db and update cursor if necessary - {:db (cond-> (assoc-in db [:chats chat-id :all-loaded?] false) - (>= clock-value cursor-clock-value) - (update-in [:chats chat-id] assoc - :cursor (chat-loading/clock-value->cursor clock-value) - :cursor-clock-value clock-value))}) - (add-sender-to-chat-users message))))) +(defn timeline-message? [db chat-id] + (and + (get-in db [:pagination-info constants/timeline-chat-id :messages-initialized?]) + (or + (= chat-id (chat-model/my-profile-chat-topic db)) + (when-let [pub-key (get-in db [:chats chat-id :profile-public-key])] + (contact.db/added? db pub-key))))) -(defn- message-loaded? - [{:keys [db]} {:keys [chat-id message-id]}] - (get-in db [:messages chat-id message-id])) +(defn get-timeline-message [db chat-id message-js] + (when (timeline-message? db chat-id) + (data-store.messages/<-rpc (types/js->clj message-js)))) -(defn- earlier-than-deleted-at? - [{:keys [db]} {:keys [chat-id clock-value]}] - (let [{:keys [deleted-at-clock-value]} - (get-in db [:chats chat-id])] - (>= deleted-at-clock-value clock-value))) +(defn add-message [{:keys [db] :as acc} message-js chat-id message-id cursor-clock-value] + (let [{:keys [alias replace from clock-value] :as message} + (data-store.messages/<-rpc (types/js->clj message-js))] + (if (message-loaded? db chat-id message-id) + ;; If the message is already loaded, it means it's an update, that + ;; happens when a message that was missing a reply had the reply + ;; coming through, in which case we just insert the new message + (assoc-in acc [:db :messages chat-id message-id] message) + (cond-> acc + ;;add new message to db + :always + (update-in [:db :messages chat-id] assoc message-id message) + :always + (update-in [:db :message-lists chat-id] message-list/add message) -(fx/defn update-unviewed-count - [{:keys [db] :as cofx} {:keys [chat-id from message-type message-id new?]}] - (when-not (= message-type constants/message-type-private-group-system-message) - (let [{:keys [current-chat-id view-id]} db - chat-view? (= :chat view-id) - current-count (get-in db [:chats chat-id :unviewed-messages-count])] - (cond - (= from (multiaccounts.model/current-public-key cofx)) - ;; nothing to do - nil + (or (not cursor-clock-value) (< clock-value cursor-clock-value)) + (update-in [:db :pagination-info chat-id] assoc + :cursor (chat.loading/clock-value->cursor clock-value) + :cursor-clock-value clock-value) - (and chat-view? (= current-chat-id chat-id)) - (fx/merge cofx - (data-store.messages/mark-messages-seen current-chat-id [message-id] nil)) + ;;conj sender for add-sender-to-chat-users + (and (not (string/blank? alias)) + (not (get-in db [:chats chat-id :users from]))) + (update :senders assoc from message) - new? - {:db (update-in db [:chats chat-id] - assoc - :unviewed-messages-count (inc current-count))})))) + (not (string/blank? replace)) + ;;TODO this is expensive + (hide-message chat-id replace))))) -(fx/defn check-for-incoming-tx - [cofx {{:keys [transaction-hash]} :command-parameters}] - (when (and transaction-hash - (not (string/blank? transaction-hash))) - ;; NOTE(rasom): dispatch later is needed because of circular dependency - {:dispatch-later - [{:dispatch [:watch-tx transaction-hash] - :ms 20}]})) +(defn reduce-js-messages [{:keys [db] :as acc} ^js message-js] + (let [chat-id (.-localChatId message-js) + clock-value (.-clock message-js) + message-id (.-id message-js) + current-chat-id (:current-chat-id db) + cursor-clock-value (get-in db [:pagination-info current-chat-id :cursor-clock-value])] + ;;ignore not opened chats and earlier clock + (if (and (get-in db [:pagination-info chat-id :messages-initialized?]) + ;;TODO why do we need this ? + (not (earlier-than-deleted-at? db chat-id clock-value))) + (if (or (not @view.state/first-not-visible-item) + (<= (:clock-value @view.state/first-not-visible-item) + clock-value)) + (add-message acc message-js chat-id message-id cursor-clock-value) + ;; Not in the current view, set all-loaded to false + ;; and offload to db and update cursor if necessary + ;;TODO if we'll offload messages , it will conflict with end reached, so probably if we reached the end of visible area, + ;; we need to drop other messages with (< clock-value cursor-clock-value) from response-js so we don't update + ;; :cursor-clock-value because it will be changed when we loadMore message + {:db (cond-> (assoc-in db [:pagination-info chat-id :all-loaded?] false) + (> clock-value cursor-clock-value) + ;;TODO cut older messages from messages-list + (update-in [:pagination-info chat-id] assoc + :cursor (chat.loading/clock-value->cursor clock-value) + :cursor-clock-value clock-value))}) + acc))) -(fx/defn receive-one - {:events [::receive-one]} - [{:keys [db] :as cofx} {:keys [message-id chat-id] :as message}] - (fx/merge cofx - ;;If its a profile updates we want to add this message to the timeline as well - #(when (get-in cofx [:db :chats chat-id :profile-public-key]) - {:dispatch-n [[::receive-one (assoc message :chat-id chat-model/timeline-chat-id)]]}) - #(when-not (earlier-than-deleted-at? cofx message) - (if (message-loaded? cofx message) - ;; If the message is already loaded, it means it's an update, that - ;; happens when a message that was missing a reply had the reply - ;; coming through, in which case we just insert the new message - {:db (assoc-in db [:messages chat-id message-id] message)} - (fx/merge cofx - (add-received-message message) - (update-unviewed-count message) - (chat-model/join-time-messages-checked chat-id) - (check-for-incoming-tx message)))))) +(defn receive-many [{:keys [db]} ^js response-js] + (let [current-chat-id (:current-chat-id db) + messages-js ^js (.splice (.-messages response-js) 0 (if platform/low-device? 3 10)) + {:keys [db chats senders]} + (reduce reduce-js-messages + {:db db :chats #{} :senders {} :transactions #{}} + messages-js)] + ;;we want to render new messages as soon as possible + ;;so we dispatch later all other events which can be handled async + {:db db -;;TODO currently we process every message, we need to precess them by batches -;;or better move processing to status-go -#_((fx/defn add-received-messages - [{:keys [db] :as cofx} grouped-messages] - (when-let [messages (get grouped-messages (:loaded-chat-id db))] - (apply fx/merge cofx (map add-received-message messages)))) + :utils/dispatch-later + (concat [{:ms 20 :dispatch [:process-response response-js]}] + (when (and current-chat-id + (get chats current-chat-id) + (not (chat-model/profile-chat? {:db db} current-chat-id))) + [{:ms 100 :dispatch [:chat/mark-all-as-read (:current-chat-id db)]}]) + (when (seq senders) + [{:ms 100 :dispatch [:chat/add-senders-to-chat-users (vals senders)]}]))})) - (defn reduce-count-messages [me] - (fn [acc chat-id messages] - (assoc acc chat-id - (remove #(or - (= (:message-type %) - constants/message-type-private-group-system-message) - (= (:from %) me)) - messages)))) +(defn reduce-js-statuses [db ^js message-js] + (let [chat-id (.-localChatId message-js) + profile-initialized (get-in db [:pagination-info chat-id :messages-initialized?]) + timeline-message (timeline-message? db chat-id)] + (if (or profile-initialized timeline-message) + (let [{:keys [message-id] :as message} (data-store.messages/<-rpc (types/js->clj message-js))] + (cond-> db + profile-initialized + (update-in [:messages chat-id] assoc message-id message) + profile-initialized + (update-in [:message-lists chat-id] message-list/add message) + timeline-message + (update-in [:messages constants/timeline-chat-id] assoc message-id message) + timeline-message + (update-in [:message-lists constants/timeline-chat-id] message-list/add message))) + db))) - (defn reduce-chat-messages [chat-view? current-chat-id] - (fn [acc chat-id messages] - (if (and chat-view? (= current-chat-id chat-id)) - (data-store.messages/mark-messages-seen acc current-chat-id (map :message-id messages) nil) - (update-in acc [:db :chats chat-id :unviewed-messages-count] + (count messages))))) +(fx/defn process-statuses + {:events [:process-statuses]} + [{:keys [db]} statuses] + {:db (reduce reduce-js-statuses db statuses)}) - (fx/defn update-unviewed-counts - [{:keys [db] :as cofx} grouped-messages] - (let [{:keys [current-chat-id view-id]} db - me (multiaccounts.model/current-public-key cofx) - messages (reduce-kv (reduce-count-messages me) - {} - grouped-messages)] - (when (seq messages) - (reduce-kv (reduce-chat-messages (= :chat view-id) current-chat-id) {:db db} messages)))) - - (fx/defn receive [cofx messages] - (when-let [grouped-messages - (->> (into [] - (comp - (map #(assoc % :chat-id (extract-chat-id cofx %))) - (remove #(earlier-than-deleted-at? cofx %))) - messages) - (group-by :chat-id))] - (when (seq grouped-messages) - (fx/merge cofx - (add-received-messages grouped-messages) - (update-unviewed-counts grouped-messages) - (chat-model/join-time-messages-checked-for-chats (keys grouped-messages))))))) - -;;;; Send message (fx/defn update-db-message-status [{:keys [db] :as cofx} chat-id message-id status] (when (get-in db [:messages chat-id message-id]) @@ -242,13 +216,9 @@ (rebuild-message-list chat-id))) (fx/defn send-message - [{:keys [db now] :as cofx} message] + [cofx message] (protocol/send-chat-messages cofx [message])) (fx/defn send-messages - [{:keys [db now] :as cofx} messages] + [cofx messages] (protocol/send-chat-messages cofx messages)) - -(fx/defn toggle-expand-message - [{:keys [db]} chat-id message-id] - {:db (update-in db [:messages chat-id message-id :expanded?] not)}) diff --git a/src/status_im/chat/models/message_list.cljs b/src/status_im/chat/models/message_list.cljs index 0eada64827..5614c5e6c6 100644 --- a/src/status_im/chat/models/message_list.cljs +++ b/src/status_im/chat/models/message_list.cljs @@ -4,6 +4,7 @@ ["functional-red-black-tree" :as rb-tree])) (defn- add-datemark [{:keys [whisper-timestamp] :as msg}] + ;;TODO this is slow (assoc msg :datemark (time/day-relative whisper-timestamp))) (defn- add-timestamp [{:keys [whisper-timestamp] :as msg}] @@ -132,12 +133,10 @@ (let [^js iter (.find tree message) ^js previous-message (when (.-hasPrev iter) (get-prev-element iter)) - ^js next-message (when (.-hasNext iter) - (get-next-element iter)) + ^js next-message (when (.-hasNext iter) + (get-next-element iter)) ^js message-with-pos-data (add-group-info message previous-message next-message)] - (cond-> - (.update iter message-with-pos-data) - + (cond-> (.update iter message-with-pos-data) next-message (-> ^js (.find next-message) (.update (update-next-message message-with-pos-data next-message))) @@ -170,8 +169,7 @@ (update-message tree prepared-message))) (defn add [message-list message] - (insert-message (or message-list (rb-tree compare-fn)) - (prepare-message message))) + (insert-message (or message-list (rb-tree compare-fn)) (prepare-message message))) (defn add-many [message-list messages] (reduce add diff --git a/src/status_im/chat/models/message_seen.cljs b/src/status_im/chat/models/message_seen.cljs index ee46a66269..3f06294f98 100644 --- a/src/status_im/chat/models/message_seen.cljs +++ b/src/status_im/chat/models/message_seen.cljs @@ -13,8 +13,7 @@ {:db (update-in db [:chats chat-id] assoc :unviewed-messages-count (subtract-seen-messages unviewed-messages-count - loaded-unviewed-messages-ids) - :loaded-unviewed-messages-ids #{})})) + loaded-unviewed-messages-ids))})) (fx/defn mark-messages-seen "Marks all unviewed loaded messages as seen in particular chat" @@ -28,11 +27,4 @@ db loaded-unviewed-ids)} (messages-store/mark-messages-seen chat-id loaded-unviewed-ids nil) - (update-chats-unviewed-messages-count {:chat-id chat-id}))))) - -(fx/defn ui-mark-messages-seen - {:events [:chat.ui/mark-messages-seen]} - [{:keys [db] :as cofx} view-id] - (fx/merge cofx - {:db (assoc db :view-id view-id)} - (mark-messages-seen cofx (:current-chat-id db)))) \ No newline at end of file + (update-chats-unviewed-messages-count {:chat-id chat-id}))))) \ No newline at end of file diff --git a/src/status_im/chat/models/message_test.cljs b/src/status_im/chat/models/message_test.cljs index be5116784e..fa3731b94c 100644 --- a/src/status_im/chat/models/message_test.cljs +++ b/src/status_im/chat/models/message_test.cljs @@ -1,155 +1,135 @@ (ns status-im.chat.models.message-test (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.chat.models.loading :as chat-loading] [status-im.chat.models.message :as message] [status-im.chat.models.message-list :as models.message-list] - [status-im.ui.screens.chat.state :as view.state] - [status-im.utils.datetime :as time])) + [status-im.chat.models.loading :as loading] + [status-im.utils.datetime :as time] + [status-im.ui.screens.chat.state :as view.state])) (deftest add-received-message-test - (with-redefs [message/add-message (constantly :added)] + (with-redefs [message/add-message #(identity %1)] (let [chat-id "chat-id" clock-value 10 cursor-clock-value (dec clock-value) - cursor (chat-loading/clock-value->cursor cursor-clock-value) + cursor (loading/clock-value->cursor cursor-clock-value) cofx {:now 0 - :db {:loaded-chat-id chat-id - :current-chat-id chat-id - :all-loaded? true - :chats {chat-id {:cursor cursor - :cursor-clock-value cursor-clock-value}}}} - message {:chat-id chat-id - :clock-value clock-value - :alias "alias" - :name "name" - :identicon "identicon" - :from "from"}] - (testing "not current-chat" - (is (nil? (message/add-received-message - (update cofx :db dissoc :loaded-chat-id) - message)))) + :db {:current-chat-id chat-id + :pagination-info {chat-id {:messages-initialized? true + :cursor cursor + :cursor-clock-value cursor-clock-value}}}} + message #js {:localChatId chat-id + :clock clock-value + :alias "alias" + :name "name" + :identicon "identicon" + :from "from"}] ;; <- cursor ;; <- message ;; <- top of the chat (testing "there's no hidden item" (with-redefs [view.state/first-not-visible-item (atom nil)] - (is (= {:db {:loaded-chat-id "chat-id", - :current-chat-id "chat-id", - :all-loaded? true, - :chats + (is (= {:db {:current-chat-id "chat-id", + :pagination-info {"chat-id" - {:cursor + {:messages-initialized? true + :cursor "00000000000000000000000000000000000000000000000000090x0000000000000000000000000000000000000000000000000000000000000000", - :cursor-clock-value 9, - :users - {"from" {:alias "alias", - :name "name", - :identicon "identicon", - :public-key "from" - :nickname nil - :searchable-phrases ["alias" "name"]}}}}}} - (message/add-received-message - cofx - message))))) + :cursor-clock-value 9}}}} + (dissoc (message/receive-many + cofx + #js {:messages (to-array [message])}) + :utils/dispatch-later))))) ;; <- cursor ;; <- first-hidden-item ;; <- message ;; <- top of the chat (testing "the hidden item has a clock value less than the current" (with-redefs [view.state/first-not-visible-item (atom {:clock-value (dec clock-value)})] - (is (= {:db {:loaded-chat-id "chat-id", - :current-chat-id "chat-id", - :all-loaded? true, - :chats + (is (= {:db {:current-chat-id "chat-id", + :pagination-info {"chat-id" - {:cursor + {:messages-initialized? true + :cursor "00000000000000000000000000000000000000000000000000090x0000000000000000000000000000000000000000000000000000000000000000", - :cursor-clock-value 9, - :users - {"from" {:alias "alias", - :name "name", - :identicon "identicon", - :public-key "from" - :nickname nil - :searchable-phrases ["alias" "name"]}}}}}} - (message/add-received-message - cofx - message))))) + :cursor-clock-value 9}}}} + (dissoc (message/receive-many + cofx + #js {:messages (to-array [message])}) + :utils/dispatch-later))))) ;; <- cursor ;; <- message ;; <- first-hidden-item ;; <- top of the chat (testing "the message falls between the first-hidden-item and cursor" (with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})] - (let [result (message/add-received-message - cofx - message)] + (let [result (dissoc (message/receive-many + cofx + #js {:messages (to-array [message])}) + :utils/dispatch-later)] (testing "it sets all-loaded? to false" - (is (not (get-in result [:db :chats chat-id :all-loaded?])))) + (is (not (get-in result [:db :pagination-info chat-id :all-loaded?])))) (testing "it updates cursor-clock-value & cursor" - (is (= clock-value (get-in result [:db :chats chat-id :cursor-clock-value]))) - (is (= (chat-loading/clock-value->cursor clock-value) (get-in result [:db :chats chat-id :cursor]))))))) + (is (= clock-value (get-in result [:db :pagination-info chat-id :cursor-clock-value]))) + (is (= (loading/clock-value->cursor clock-value) (get-in result [:db :pagination-info chat-id :cursor]))))))) ;; <- message ;; <- first-hidden-item ;; <- top of the chat (testing "the message falls between the first-hidden-item and cursor is nil" (with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})] - (let [result (message/add-received-message - (update-in cofx [:db :chats chat-id] dissoc :cursor :cursor-clock-value) - message)] + (let [result (dissoc (message/receive-many + (update-in cofx [:db :pagination-info chat-id] dissoc :cursor :cursor-clock-value) + #js {:messages (to-array [message])}) + :utils/dispatch-later)] (testing "it sets all-loaded? to false" - (is (not (get-in result [:db :chats chat-id :all-loaded?])))) + (is (not (get-in result [:db :pagination-info chat-id :all-loaded?])))) (testing "it updates cursor-clock-value & cursor" - (is (= clock-value (get-in result [:db :chats chat-id :cursor-clock-value]))) - (is (= (chat-loading/clock-value->cursor clock-value) (get-in result [:db :chats chat-id :cursor]))))))) + (is (= clock-value (get-in result [:db :pagination-info chat-id :cursor-clock-value]))) + (is (= (loading/clock-value->cursor clock-value) (get-in result [:db :pagination-info chat-id :cursor]))))))) ;; <- message ;; <- cursor ;; <- first-hidden-item ;; <- top of the chat (testing "the message falls before both the first-hidden-item and cursor" (with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})] - (let [result (message/add-received-message - cofx - (update message :clock-value #(- % 2)))] + (let [message #js {:localChatId chat-id + :clock (- clock-value 2) + :alias "alias" + :name "name" + :identicon "identicon" + :from "from"} + result (dissoc (message/receive-many + cofx + #js {:messages (to-array [message])}) + :utils/dispatch-later)] (testing "it sets all-loaded? to false" - (is (not (get-in result [:db :chats chat-id :all-loaded?])))) + (is (not (get-in result [:db :pagination-info chat-id :all-loaded?])))) (testing "it does not update cursor-clock-value & cursor" - (is (= cursor-clock-value (get-in result [:db :chats chat-id :cursor-clock-value]))) - (is (= cursor (get-in result [:db :chats chat-id :cursor])))))))))) + (is (= cursor-clock-value (get-in result [:db :pagination-info chat-id :cursor-clock-value]))) + (is (= cursor (get-in result [:db :pagination-info chat-id :cursor])))))))))) (deftest message-loaded? (testing "it returns false when it's not in loaded message" - (is (not (#'status-im.chat.models.message/message-loaded? {:db {:chats {"a" {}}}} - {:message-id "message-id" - :from "a" - :clock-value 1 - :chat-id "a"})))) + (is (not (#'status-im.chat.models.message/message-loaded? {:messages {"a" {}}} "a" "message-id")))) (testing "it returns true when it's already in the loaded message" - (is (#'status-im.chat.models.message/message-loaded? {:db - {:messages {"a" {"message-id" {}}}}} - {:message-id "message-id" - :from "a" - :clock-value 1 - :chat-id "a"})))) + (is (#'status-im.chat.models.message/message-loaded? {:messages {"a" {"message-id" {}}}} "a" "message-id")))) + (deftest earlier-than-deleted-at? (testing "it returns true when the clock-value is the same as the deleted-clock-value in chat" - (is (#'status-im.chat.models.message/earlier-than-deleted-at? {:db {:chats {"a" {:deleted-at-clock-value 1}}}} - {:message-id "message-id" - :from "a" - :clock-value 1 - :chat-id "a"}))) + (is (#'status-im.chat.models.message/earlier-than-deleted-at? + {:chats {"a" {:deleted-at-clock-value 1}}} + "a" + 1))) (testing "it returns false when the clock-value is greater than the deleted-clock-value in chat" - (is (not (#'status-im.chat.models.message/earlier-than-deleted-at? {:db {:chats {"a" {:deleted-at-clock-value 1}}}} - {:message-id "message-id" - :from "a" - :clock-value 2 - :chat-id "a"})))) + (is (not (#'status-im.chat.models.message/earlier-than-deleted-at? + {:chats {"a" {:deleted-at-clock-value 1}}} + "a" + 2)))) + (testing "it returns true when the clock-value is less than the deleted-clock-value in chat" - (is (#'status-im.chat.models.message/earlier-than-deleted-at? {:db {:chats {"a" {:deleted-at-clock-value 1}}}} - {:message-id "message-id" - :from "a" - :clock-value 0 - :chat-id "a"})))) + (is (#'status-im.chat.models.message/earlier-than-deleted-at? + {:chats {"a" {:deleted-at-clock-value 1}}} + "a" + 0)))) (deftest delete-message (with-redefs [time/day-relative (constantly "day-relative") diff --git a/src/status_im/chat/models/reactions.cljs b/src/status_im/chat/models/reactions.cljs index eff4550b2f..7654466520 100644 --- a/src/status_im/chat/models/reactions.cljs +++ b/src/status_im/chat/models/reactions.cljs @@ -4,8 +4,7 @@ [status-im.utils.fx :as fx] [taoensso.timbre :as log] [status-im.transport.message.protocol :as message.protocol] - [status-im.data-store.reactions :as data-store.reactions] - [status-im.chat.models :as chat])) + [status-im.data-store.reactions :as data-store.reactions])) (defn update-reaction [acc retracted chat-id message-id emoji-id emoji-reaction-id reaction] ;; NOTE(Ferossgp): For a better performance, better to not keep in db all retracted reactions @@ -23,7 +22,7 @@ :as reaction}] (cond-> (update-reaction acc retracted chat-id message-id emoji-id emoji-reaction-id reaction) (get-in chats [chat-id :profile-public-key]) - (update-reaction retracted chat/timeline-chat-id message-id emoji-id emoji-reaction-id reaction))) + (update-reaction retracted constants/timeline-chat-id message-id emoji-id emoji-reaction-id reaction))) reactions new-reactions))) @@ -39,27 +38,25 @@ {:db (update db :reactions (process-reactions (:chats db)) reactions)})) (fx/defn load-more-reactions - [{:keys [db] :as cofx} cursor] - (when-let [current-chat-id (:current-chat-id db)] - (when-let [session-id (get-in db [:pagination-info current-chat-id :messages-initialized?])] - (data-store.reactions/reactions-by-chat-id-rpc - current-chat-id - cursor - constants/default-number-of-messages - #(re-frame/dispatch [::reactions-loaded current-chat-id session-id %]) - #(log/error "failed loading reactions" current-chat-id %))))) + {:events [:load-more-reactions]} + [{:keys [db]} cursor chat-id] + (when-let [session-id (get-in db [:pagination-info chat-id :messages-initialized?])] + (data-store.reactions/reactions-by-chat-id-rpc + chat-id + cursor + constants/default-number-of-messages + #(re-frame/dispatch [::reactions-loaded chat-id session-id %]) + #(log/error "failed loading reactions" chat-id %)))) (fx/defn reactions-loaded {:events [::reactions-loaded]} - [{{:keys [current-chat-id] :as db} :db} + [{db :db} chat-id session-id reactions] - (when-not (or (nil? current-chat-id) - (not= chat-id current-chat-id) - (and (get-in db [:pagination-info current-chat-id :messages-initialized?]) - (not= session-id - (get-in db [:pagination-info current-chat-id :messages-initialized?])))) + (when-not (and (get-in db [:pagination-info chat-id :messages-initialized?]) + (not= session-id + (get-in db [:pagination-info chat-id :messages-initialized?]))) (let [reactions-w-chat-id (map #(assoc % :chat-id chat-id) reactions)] {:db (update db :reactions (process-reactions (:chats db)) reactions-w-chat-id)}))) diff --git a/src/status_im/chat/models/transport.cljs b/src/status_im/chat/models/transport.cljs index ef2ae91c94..1c318d09c1 100644 --- a/src/status_im/chat/models/transport.cljs +++ b/src/status_im/chat/models/transport.cljs @@ -1,3 +1,4 @@ +;;this ns is needed because of cycled deps (ns status-im.chat.models.transport (:require [status-im.utils.fx :as fx] [status-im.transport.message.core :as transport.message] @@ -9,5 +10,5 @@ (let [message (get-in db [:messages chat-id message-id])] (fx/merge cofx - (transport.message/set-message-envelope-hash chat-id message-id (:message-type message) 1) + (transport.message/set-message-envelope-hash chat-id message-id (:message-type message)) (chat.message/resend-message chat-id message-id)))) \ No newline at end of file diff --git a/src/status_im/chat/models_test.cljs b/src/status_im/chat/models_test.cljs index 7b6948a754..a8c368175b 100644 --- a/src/status_im/chat/models_test.cljs +++ b/src/status_im/chat/models_test.cljs @@ -151,7 +151,6 @@ :messages {"status" {"4" {} "5" {} "6" {}}} :chats {"status" {:public? true - :group-chat true - :loaded-unviewed-messages-ids #{"6" "5" "4"}} - "opened" {:loaded-unviewed-messages-ids #{}} - "1-1" {:loaded-unviewed-messages-ids #{"6" "5" "4"}}}}) + :group-chat true} + "opened" {} + "1-1" {}}}) diff --git a/src/status_im/commands/core.cljs b/src/status_im/commands/core.cljs index 8f708b71d3..21f2f9cdc4 100644 --- a/src/status_im/commands/core.cljs +++ b/src/status_im/commands/core.cljs @@ -24,18 +24,21 @@ {:db (dissoc db :commands/select-account) ::json-rpc/call [{:method (json-rpc/call-ext-method "acceptRequestAddressForTransaction") :params [message-id address] - :on-success #(re-frame/dispatch [:transport/message-sent % 1])}]}) + :js-response true + :on-success #(re-frame/dispatch [:transport/message-sent %])}]}) (fx/defn handle-decline-request-address-for-transaction {:events [::decline-request-address-for-transaction]} [cofx message-id] {::json-rpc/call [{:method (json-rpc/call-ext-method "declineRequestAddressForTransaction") :params [message-id] - :on-success #(re-frame/dispatch [:transport/message-sent % 1])}]}) + :js-response true + :on-success #(re-frame/dispatch [:transport/message-sent %])}]}) (fx/defn handle-decline-request-transaction {:events [::decline-request-transaction]} [cofx message-id] {::json-rpc/call [{:method (json-rpc/call-ext-method "declineRequestTransaction") :params [message-id] - :on-success #(re-frame/dispatch [:transport/message-sent % 1])}]}) + :js-response true + :on-success #(re-frame/dispatch [:transport/message-sent %])}]}) diff --git a/src/status_im/communities/core.cljs b/src/status_im/communities/core.cljs index ab47c9052a..b23d448204 100644 --- a/src/status_im/communities/core.cljs +++ b/src/status_im/communities/core.cljs @@ -175,8 +175,9 @@ :text "Upgrade here to see an invitation to community" :communityId community-id :contentType constants/content-type-community}] + :js-response true :on-success - #(re-frame/dispatch [:transport/message-sent % 1]) + #(re-frame/dispatch [:transport/message-sent %]) :on-failure #(log/error "failed to send a message" %)}]}) (fx/defn invite-users @@ -242,7 +243,7 @@ [{:keys [db]}] (let [{:keys [name description membership]} (get db :communities/create) my-public-key (get-in db [:multiaccount :public-key])] - (log/error "Edit community is not yet implemented") + (log/error "Edit community is not yet implemented"))) ;; {::json-rpc/call [{:method "wakuext_editCommunity" ;; :params [{:identity {:display_name name ;; :description description} @@ -251,7 +252,7 @@ ;; :on-error #(do ;; (log/error "failed to create community" %) ;; (re-frame/dispatch [::failed-to-edit-community %]))}]} - )) + (fx/defn create-channel {:events [::create-channel-confirmation-pressed]} diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 6519faf2d9..81145a3ce8 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -132,3 +132,5 @@ (def ^:const keycard-integration-link "https://status.im/keycard-integration") (def ^:const status-community-id "0x039b2da47552aa117a96ea8f1d4d108ba66637c7517a3c94a57b99dbb8a002eda2") + +(def ^:const timeline-chat-id "@timeline70bd746ddcc12beb96b2c9d572d0784ab137ffc774f5383e50585a932080b57cca0484b259e61cecbaa33a4c98a300a") diff --git a/src/status_im/contact/block.cljs b/src/status_im/contact/block.cljs index 61ab3c356f..9098d9c144 100644 --- a/src/status_im/contact/block.cljs +++ b/src/status_im/contact/block.cljs @@ -43,7 +43,7 @@ public-key) (assoc :last-updated now) (update :system-tags (fnil conj #{}) :contact/blocked)) - from-one-to-one-chat? (not (get-in db [:chats (:inactive-chat-id db) :group-chat]))] + from-one-to-one-chat? (not (get-in db [:chats (:current-chat-id db) :group-chat]))] (fx/merge cofx {:db (-> db ;; add the contact to blocked contacts diff --git a/src/status_im/contact/core.cljs b/src/status_im/contact/core.cljs index 23051aec36..35e3ec4a34 100644 --- a/src/status_im/contact/core.cljs +++ b/src/status_im/contact/core.cljs @@ -8,7 +8,8 @@ [status-im.navigation :as navigation] [status-im.utils.fx :as fx] [taoensso.timbre :as log] - [clojure.string :as string])) + [clojure.string :as string] + [status-im.constants :as constants])) (fx/defn load-contacts {:events [::contacts-loaded]} @@ -81,7 +82,8 @@ (fnil #(conj % :contact/added) #{})))] (fx/merge cofx {:db (dissoc db :contacts/new-identity) - :dispatch [:start-profile-chat public-key]} + :dispatch-n [[:start-profile-chat public-key] + [:offload-messages constants/timeline-chat-id]]} (upsert-contact contact) (send-contact-request contact) (mailserver/process-next-messages-request))))) @@ -95,7 +97,7 @@ (fnil #(disj % :contact/added) #{}))] (fx/merge cofx {:db (assoc-in db [:contacts/contacts public-key] new-contact) - :dispatch [:chat/remove-update-chat public-key]} + :dispatch [:offload-messages constants/timeline-chat-id]} (contacts-store/save-contact new-contact)))) (fx/defn create-contact diff --git a/src/status_im/data_store/chats.cljs b/src/status_im/data_store/chats.cljs index b9b086d104..169e81834f 100644 --- a/src/status_im/data_store/chats.cljs +++ b/src/status_im/data_store/chats.cljs @@ -83,8 +83,7 @@ :last-clock-value :lastClockValue :profile-public-key :profile}) (dissoc :public? :group-chat :messages - :might-have-join-time-messages? - :loaded-unviewed-messages-ids :chat-type + :might-have-join-time-messages? :chat-type :contacts :admins :members-joined))) (defn <-rpc [chat] diff --git a/src/status_im/data_store/chats_test.cljs b/src/status_im/data_store/chats_test.cljs index 70efced626..7964fb3f1b 100644 --- a/src/status_im/data_store/chats_test.cljs +++ b/src/status_im/data_store/chats_test.cljs @@ -16,7 +16,6 @@ :unviewed-messages-count 2 :is-active true :chat-id "chat-id" - :loaded-unviewed-messages-ids [] :timestamp 2} expected-chat {:id "chat-id" :color "color" diff --git a/src/status_im/data_store/messages.cljs b/src/status_im/data_store/messages.cljs index 01711e6f41..0e646fbad1 100644 --- a/src/status_im/data_store/messages.cljs +++ b/src/status_im/data_store/messages.cljs @@ -57,8 +57,8 @@ limit on-success on-failure] - {::json-rpc/call [{:method (json-rpc/call-ext-method "chatMessages") - :params [chat-id cursor limit] + {::json-rpc/call [{:method (json-rpc/call-ext-method "chatMessages") + :params [chat-id cursor limit] :on-success (fn [result] (on-success (update result :messages #(map <-rpc %)))) :on-failure on-failure}]}) diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index 017144a24b..e18fb77d52 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -200,7 +200,7 @@ (str "wakuext_" method)) (defn call - [{:keys [method params on-success on-error] :as arg}] + [{:keys [method params on-success on-error js-response] :as arg}] (if-let [method-options (json-rpc-api method)] (let [params (or params []) {:keys [id on-result subscription?] @@ -226,15 +226,16 @@ (fn [response] (if (string/blank? response) (on-error {:message "Blank response"}) - (let [{:keys [error result]} (types/json->clj response)] - (if error - (on-error error) + (let [response-js (types/json->js response)] + (if (.-error response-js) + (on-error (types/js->clj (.-error response-js))) (if subscription? (re-frame/dispatch [:ethereum.callback/subscription-success - result on-success]) - (on-success (on-result result)))))))))) - + (types/js->clj (.-result response-js)) on-success]) + (on-success (on-result (if js-response + (.-result response-js) + (types/js->clj (.-result response-js))))))))))))) (log/warn "method" method "not found" arg))) (defn eth-call diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index daa97e95e3..3b0c3e5e37 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -1,16 +1,9 @@ (ns status-im.events (:require [re-frame.core :as re-frame] [status-im.chat.models :as chat] - [status-im.chat.models.message :as chat.message] - [status-im.chat.models.reactions :as chat.reactions] - [status-im.data-store.chats :as data-store.chats] - [status-im.data-store.messages :as data-store.messages] - [status-im.data-store.reactions :as data-store.reactions] - [status-im.group-chats.core :as group-chats] [status-im.i18n.i18n :as i18n] [status-im.mailserver.core :as mailserver] [status-im.multiaccounts.core :as multiaccounts] - [status-im.transport.message.core :as transport.message] [status-im.ui.components.react :as react] [status-im.utils.fx :as fx] status-im.utils.logging.core @@ -22,8 +15,6 @@ [status-im.native-module.core :as status] [status-im.ui.components.permissions :as permissions] [status-im.utils.utils :as utils] - [status-im.data-store.invitations :as data-store.invitations] - [status-im.contact.db :as contact.db] [status-im.ethereum.json-rpc :as json-rpc] clojure.set status-im.currency.core @@ -55,7 +46,6 @@ status-im.mailserver.constants status-im.ethereum.subscriptions status-im.fleet.core - status-im.chat.models.message-seen status-im.contact.block status-im.contact.core status-im.contact.chat @@ -101,30 +91,6 @@ (fn [] (dimensions/add-event-listener))) -;;TODO: map fx/merge is slow, we shouldn't do it, its much better to have one fx for vector of items -(fx/defn handle-update - {:events [:transport/reaction-sent :transport/retraction-sent :transport/invitation-sent]} - [cofx {:keys [chats messages emojiReactions invitations]}] - (let [chats (map data-store.chats/<-rpc chats) - messages (map data-store.messages/<-rpc messages) - message-fxs (map chat.message/receive-one messages) - emoji-reactions (map data-store.reactions/<-rpc emojiReactions) - emoji-react-fxs (map chat.reactions/receive-one emoji-reactions) - invitations-fxs [(group-chats/handle-invitations - (map data-store.invitations/<-rpc invitations))] - chat-fxs (map #(chat/ensure-chat (dissoc % :unviewed-messages-count)) chats)] - (apply fx/merge cofx (concat chat-fxs message-fxs emoji-react-fxs invitations-fxs)))) - -(fx/defn transport-message-sent - {:events [:transport/message-sent]} - [cofx response messages-count] - (let [set-hash-fxs (map (fn [{:keys [localChatId id messageType]}] - (transport.message/set-message-envelope-hash localChatId id messageType messages-count)) - (:messages response))] - (apply fx/merge cofx - (conj set-hash-fxs - (handle-update response))))) - (fx/defn dismiss-keyboard {:events [:dismiss-keyboard]} [_] @@ -210,35 +176,15 @@ [{:keys [db]} dimensions] {:db (assoc db :dimensions/window (dimensions/window dimensions))}) -(fx/defn reset-current-profile-chat [{:keys [db] :as cofx} public-key] - (let [chat-id (chat/profile-chat-topic public-key)] - (when-not (= (:current-chat-id db) chat-id) - (fx/merge cofx - (chat/start-profile-chat public-key) - (chat/offload-all-messages) - (chat/preload-chat-data chat-id))))) +(fx/defn init-timeline-chat + {:events [:init-timeline-chat]} + [{:keys [db] :as cofx}] + (when-not (get-in db [:pagination-info constants/timeline-chat-id :messages-initialized?]) + (chat/preload-chat-data cofx constants/timeline-chat-id))) -(fx/defn reset-current-timeline-chat [{:keys [db] :as cofx}] - (let [profile-chats (conj (map :public-key (contact.db/get-active-contacts (:contacts/contacts db))) - (get-in db [:multiaccount :public-key]))] - (when-not (= (:current-chat-id db) chat/timeline-chat-id) - (fx/merge cofx - (fn [cofx] - (apply fx/merge cofx (map chat/start-profile-chat profile-chats))) - (chat/start-timeline-chat) - (chat/offload-all-messages) - (chat/preload-chat-data chat/timeline-chat-id))))) - -(fx/defn reset-current-chat [{:keys [db] :as cofx} chat-id] - (when-not (= (:current-chat-id db) chat-id) - (fx/merge cofx - (chat/offload-all-messages) - (chat/preload-chat-data chat-id)))) - -;; NOTE: Will be removed with the keycard PR (fx/defn on-will-focus {:events [:screens/on-will-focus]} - [{:keys [db] :as cofx} view-id] + [cofx view-id] (fx/merge cofx #(case view-id :keycard-settings (keycard/settings-screen-did-load %) @@ -247,30 +193,8 @@ :keycard-login-pin (keycard/enter-pin-screen-did-load %) :add-new-account-pin (keycard/enter-pin-screen-did-load %) :keycard-authentication-method (keycard/authentication-method-screen-did-load %) - (:chat :group-chat-profile) (reset-current-chat % (get db :inactive-chat-id)) :multiaccounts (keycard/multiaccounts-screen-did-load %) (:wallet-stack :wallet) (wallet/wallet-will-focus %) - (:status :status-stack) - (reset-current-timeline-chat %) - :profile - (reset-current-profile-chat % (get-in % [:db :contacts/identity])) - nil))) - -(fx/defn tab-will-change - {:events [:screens/tab-will-change]} - [{:keys [db] :as cofx} view-id] - (fx/merge cofx - #(case view-id - ;;when we back to chat we want to show inactive chat - :chat - (reset-current-chat % (get db :inactive-chat-id)) - - (:status :status-stack) - (reset-current-timeline-chat %) - - :profile - (reset-current-profile-chat % (get-in % [:db :contacts/identity])) - nil))) ;;TODO :replace by named events diff --git a/src/status_im/group_chats/core.cljs b/src/status_im/group_chats/core.cljs index daa9ecba6c..a7e2fefb65 100644 --- a/src/status_im/group_chats/core.cljs +++ b/src/status_im/group_chats/core.cljs @@ -4,9 +4,6 @@ [clojure.string :as string] [re-frame.core :as re-frame] [status-im.chat.models :as models.chat] - [status-im.chat.models.message :as models.message] - [status-im.data-store.chats :as data-store.chats] - [status-im.data-store.messages :as data-store.messages] [status-im.ethereum.json-rpc :as json-rpc] [status-im.group-chats.db :as group-chats.db] [status-im.multiaccounts.model :as multiaccounts.model] @@ -16,13 +13,27 @@ [status-im.constants :as constants] [status-im.i18n.i18n :as i18n])) +(fx/defn navigate-chat-updated + {:events [:navigate-chat-updated]} + [cofx chat-id] + (if (get-in cofx [:db :chats chat-id :is-active]) + (models.chat/navigate-to-chat cofx chat-id) + (navigation/navigate-to-cofx cofx :home {}))) + +(fx/defn handle-chat-update + {:events [:chat-updated]} + [_ response] + {:dispatch-n [[:sanitize-messages-and-process-response response] + [:navigate-chat-updated (.-id (aget (.-chats response) 0))]]}) + (fx/defn remove-member "Format group update message and sign membership" {:events [:group-chats.ui/remove-member-pressed]} [_ chat-id member] {::json-rpc/call [{:method (json-rpc/call-ext-method "removeMemberFromGroupChat") :params [nil chat-id member] - :on-success #(re-frame/dispatch [::chat-updated %])}]}) + :js-response true + :on-success #(re-frame/dispatch [:chat-updated %])}]}) (fx/defn set-up-filter "Listen/Tear down the shared topic/contact-codes. Stop listening for members who @@ -39,34 +50,13 @@ (transport.filters/upsert-group-chat-topics) (transport.filters/load-members members))))) -(fx/defn handle-chat-update - {:events [::chat-updated]} - [cofx {:keys [chats messages]}] - (let [{:keys [chat-id] :as chat} (-> chats - first - (data-store.chats/<-rpc)) - - previous-chat (get-in cofx [:chats chat-id]) - set-up-filter-fx (set-up-filter chat-id previous-chat) - chat-fx (models.chat/ensure-chat - (dissoc chat :unviewed-messages-count)) - messages-fx (map #(models.message/receive-one - (data-store.messages/<-rpc %)) - messages) - navigate-fx #(if (get-in % [:db :chats chat-id :is-active]) - (models.chat/navigate-to-chat % chat-id) - (navigation/navigate-to-cofx % :home {}))] - - (apply fx/merge cofx (concat [chat-fx] - messages-fx - [navigate-fx])))) - (fx/defn join-chat {:events [:group-chats.ui/join-pressed]} [_ chat-id] {::json-rpc/call [{:method (json-rpc/call-ext-method "confirmJoiningGroup") :params [chat-id] - :on-success #(re-frame/dispatch [::chat-updated %])}]}) + :js-response true + :on-success #(re-frame/dispatch [:chat-updated %])}]}) (fx/defn create {:events [:group-chats.ui/create-pressed] @@ -75,7 +65,8 @@ (let [selected-contacts (:group/selected-contacts db)] {::json-rpc/call [{:method (json-rpc/call-ext-method "createGroupChatWithMembers") :params [nil group-name (into [] selected-contacts)] - :on-success #(re-frame/dispatch [::chat-updated %])}]})) + :js-response true + :on-success #(re-frame/dispatch [:chat-updated %])}]})) (fx/defn create-from-link [cofx {:keys [chat-id invitation-admin chat-name]}] @@ -83,14 +74,16 @@ (models.chat/navigate-to-chat cofx chat-id) {::json-rpc/call [{:method (json-rpc/call-ext-method "createGroupChatFromInvitation") :params [chat-name chat-id invitation-admin] - :on-success #(re-frame/dispatch [::chat-updated %])}]})) + :js-response true + :on-success #(re-frame/dispatch [:chat-updated %])}]})) (fx/defn make-admin {:events [:group-chats.ui/make-admin-pressed]} [_ chat-id member] {::json-rpc/call [{:method (json-rpc/call-ext-method "addAdminsToGroupChat") :params [nil chat-id [member]] - :on-success #(re-frame/dispatch [::chat-updated %])}]}) + :js-response true + :on-success #(re-frame/dispatch [:chat-updated %])}]}) (fx/defn add-members "Add members to a group chat" @@ -98,7 +91,8 @@ [{{:keys [current-chat-id selected-participants]} :db :as cofx}] {::json-rpc/call [{:method (json-rpc/call-ext-method "addMembersToGroupChat") :params [nil current-chat-id selected-participants] - :on-success #(re-frame/dispatch [::chat-updated %])}]}) + :js-response true + :on-success #(re-frame/dispatch [:chat-updated %])}]}) (fx/defn add-members-from-invitation "Add members to a group chat" @@ -107,7 +101,8 @@ {:db (assoc-in db [:group-chat/invitations id :state] constants/invitation-state-approved) ::json-rpc/call [{:method (json-rpc/call-ext-method "addMembersToGroupChat") :params [nil current-chat-id [participant]] - :on-success #(re-frame/dispatch [::chat-updated %])}]}) + :js-response true + :on-success #(re-frame/dispatch [:chat-updated %])}]}) (fx/defn leave "Leave chat" @@ -115,7 +110,8 @@ [{:keys [db] :as cofx} chat-id] {::json-rpc/call [{:method (json-rpc/call-ext-method "leaveGroupChat") :params [nil chat-id true] - :on-success #(re-frame/dispatch [::chat-updated %])}]}) + :js-response true + :on-success #(re-frame/dispatch [:chat-updated %])}]}) (fx/defn remove "Remove chat" @@ -142,7 +138,8 @@ {:db (assoc-in db [:chats chat-id :name] new-name) ::json-rpc/call [{:method (json-rpc/call-ext-method "changeGroupChatName") :params [nil chat-id new-name] - :on-success #(re-frame/dispatch [::chat-updated %])}]})) + :js-response true + :on-success #(re-frame/dispatch [:chat-updated %])}]})) (fx/defn membership-retry {:events [:group-chats.ui/membership-retry]} @@ -163,7 +160,7 @@ {:db (assoc-in db [:chat/memberships current-chat-id] nil) ::json-rpc/call [{:method (json-rpc/call-ext-method "sendGroupChatInvitationRequest") :params [nil current-chat-id invitation-admin message] - :on-success #(re-frame/dispatch [:transport/invitation-sent %])}]})) + :on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]})) (fx/defn send-group-chat-membership-rejection "Send group chat membership rejection" @@ -171,7 +168,7 @@ [cofx invitation-id] {::json-rpc/call [{:method (json-rpc/call-ext-method "sendGroupChatInvitationRejection") :params [nil invitation-id] - :on-success #(re-frame/dispatch [:transport/invitation-sent %])}]}) + :on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]}) (fx/defn handle-invitations [{db :db} invitations] diff --git a/src/status_im/mailserver/core.cljs b/src/status_im/mailserver/core.cljs index 0a83d000a1..abec6e3737 100644 --- a/src/status_im/mailserver/core.cljs +++ b/src/status_im/mailserver/core.cljs @@ -1256,7 +1256,9 @@ {}) (dismiss-connection-error false)))) -(fx/defn load-gaps-fx [{:keys [db] :as cofx} chat-id] +(fx/defn load-gaps-fx + {:events [:load-gaps]} + [{:keys [db] :as cofx} chat-id] (when-not (get-in db [:gaps-loaded? chat-id]) (let [success-fn #(re-frame/dispatch [::gaps-loaded %1 %2])] (data-store.mailservers/load-gaps cofx chat-id success-fn)))) diff --git a/src/status_im/mailserver/topics.cljs b/src/status_im/mailserver/topics.cljs index 00206b4554..20adea5bb1 100644 --- a/src/status_im/mailserver/topics.cljs +++ b/src/status_im/mailserver/topics.cljs @@ -169,9 +169,3 @@ (extract-topics (:mailserver/topics db) chat-id (not (get-in (:chats db) [chat-id :public?])))) - -(defn topics-for-current-chat - "return a list of topics used by the current-chat, include discovery if - private group chat or one-to-one" - [{:keys [current-chat-id] :as db}] - (topics-for-chat db current-chat-id)) diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index 0936c4064f..ffd622562e 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -32,7 +32,8 @@ [status-im.data-store.invitations :as data-store.invitations] [status-im.chat.models.link-preview :as link-preview] [status-im.utils.mobile-sync :as utils.mobile-sync] - [status-im.async-storage.core :as async-storage])) + [status-im.async-storage.core :as async-storage] + [status-im.chat.models :as chat.models])) (re-frame/reg-fx ::login @@ -317,6 +318,8 @@ (finish-keycard-setup) (transport/start-messenger) (communities/fetch) + (chat.models/start-timeline-chat) + (chat.models/start-profile-chat (:public-key multiaccount)) (multiaccounts/switch-preview-privacy-mode-flag) (link-preview/request-link-preview-whitelist) (logging/set-log-level (:log-level multiaccount))))) diff --git a/src/status_im/notifications/local.cljs b/src/status_im/notifications/local.cljs index 6aa27bfa57..34efdaddce 100644 --- a/src/status_im/notifications/local.cljs +++ b/src/status_im/notifications/local.cljs @@ -138,6 +138,7 @@ {:keys [identicon]} :contact contact-id :contact-id}] (when (and chat-type chat-id) + ;;TODO : DON'T USE SUBS IN EVENTS (let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity contact-id]) group-chat? (not= chat-type constants/one-to-one-chat-type) diff --git a/src/status_im/signals/core.cljs b/src/status_im/signals/core.cljs index 43dd25230a..a146cfe98c 100644 --- a/src/status_im/signals/core.cljs +++ b/src/status_im/signals/core.cljs @@ -55,7 +55,7 @@ "node.login" (status-node-started cofx (js->clj event-js :keywordize-keys true)) "envelope.sent" (transport.message/update-envelopes-status cofx (:ids (js->clj event-js :keywordize-keys true)) :sent) "envelope.expired" (transport.message/update-envelopes-status cofx (:ids (js->clj event-js :keywordize-keys true)) :not-sent) - "message.delivered" (let [{:keys [chatID messageID] :as event-cljs} (js->clj event-js :keywordize-keys true)] + "message.delivered" (let [{:keys [chatID messageID]} (js->clj event-js :keywordize-keys true)] (models.message/update-db-message-status cofx chatID messageID :delivered)) "mailserver.request.completed" (mailserver/handle-request-completed cofx (js->clj event-js :keywordize-keys true)) "mailserver.request.expired" (when (multiaccounts.model/logged-in? cofx) @@ -64,7 +64,7 @@ "subscriptions.data" (ethereum.subscriptions/handle-signal cofx (js->clj event-js :keywordize-keys true)) "subscriptions.error" (ethereum.subscriptions/handle-error cofx (js->clj event-js :keywordize-keys true)) "whisper.filter.added" (transport.filters/handle-negotiated-filter cofx (js->clj event-js :keywordize-keys true)) - "messages.new" (transport.message/process-response cofx event-js) + "messages.new" (transport.message/sanitize-messages-and-process-response cofx event-js) "wallet" (ethereum.subscriptions/new-wallet-event cofx (js->clj event-js :keywordize-keys true)) "local-notifications" (local-notifications/process cofx (js->clj event-js :keywordize-keys true)) (log/debug "Event " type " not handled")))) diff --git a/src/status_im/signing/core.cljs b/src/status_im/signing/core.cljs index 10c5a55e42..d84af45911 100644 --- a/src/status_im/signing/core.cljs +++ b/src/status_im/signing/core.cljs @@ -240,8 +240,9 @@ :params [chat-id (str value) contract transaction-hash (or (:result (types/json->clj signature)) (ethereum/normalized-hex signature))] + :js-response true :on-success - #(re-frame/dispatch [:transport/message-sent % 1])}]}) + #(re-frame/dispatch [:transport/message-sent %])}]}) (fx/defn send-accept-request-transaction-message {:events [:sign/send-accept-transaction-message]} @@ -250,8 +251,9 @@ :params [transaction-hash message-id (or (:result (types/json->clj signature)) (ethereum/normalized-hex signature))] + :js-response true :on-success - #(re-frame/dispatch [:transport/message-sent % 1])}]}) + #(re-frame/dispatch [:transport/message-sent %])}]}) (fx/defn transaction-result [{:keys [db] :as cofx} result tx-obj] @@ -429,7 +431,8 @@ amount (when-not (= symbol :ETH) address)] - :on-success #(re-frame/dispatch [:transport/message-sent % 1])}]}))) + :js-response true + :on-success #(re-frame/dispatch [:transport/message-sent %])}]}))) (fx/defn sign-transaction-button-clicked-from-request {:events [:wallet.ui/sign-transaction-button-clicked-from-request]} diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 9dfcfa8a15..5308280de8 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -116,7 +116,6 @@ (reg-root-key-sub :group-chat/invitations :group-chat/invitations) (reg-root-key-sub :chats/mention-suggestions :chats/mention-suggestions) (reg-root-key-sub :chat/inputs-with-mentions :chat/inputs-with-mentions) -(reg-root-key-sub :inactive-chat-id :inactive-chat-id) ;;browser (reg-root-key-sub :browsers :browser/browsers) (reg-root-key-sub :browser/options :browser/options) @@ -758,7 +757,7 @@ chats))) (re-frame/reg-sub - ::chat + :chat-by-id :<- [::chats] (fn [chats [_ chat-id]] (get chats chat-id))) @@ -777,6 +776,19 @@ (fn [[chat-id inputs]] (get inputs chat-id))) +(re-frame/reg-sub + :chats/timeline-chat-input + :<- [:chat/inputs] + :<- [:multiaccount/public-key] + (fn [[inputs public-key]] + (get inputs (chat.models/profile-chat-topic public-key)))) + +(re-frame/reg-sub + :chats/timeline-chat-input-text + :<- [:chats/timeline-chat-input] + (fn [input] + (:input-text input))) + (re-frame/reg-sub :chats/current-chat-input-text :<- [:chats/current-chat-inputs] @@ -794,13 +806,11 @@ :chats/current-chat :<- [:chats/current-raw-chat] :<- [:multiaccount/public-key] - :<- [:inactive-chat-id] :<- [:communities/current-community] (fn [[{:keys [group-chat] :as current-chat} my-public-key - inactive-chat-id community]] - (when (and current-chat (= (:chat-id current-chat) inactive-chat-id)) + (when current-chat (cond-> current-chat (chat.models/public-chat? current-chat) (assoc :show-input? true) @@ -846,27 +856,24 @@ (chat.models/public-chat? current-chat))) (re-frame/reg-sub - :chats/current-chat-messages + :chats/chat-messages :<- [::messages] - :<- [:chats/current-chat-id] - (fn [[messages chat-id]] + (fn [messages [_ chat-id]] (get messages chat-id {}))) (re-frame/reg-sub :chats/message-reactions :<- [:multiaccount/public-key] :<- [::reactions] - :<- [:chats/current-chat-id] - (fn [[current-public-key reactions current-chat-id] [_ message-id chat-id]] + (fn [[current-public-key reactions] [_ message-id chat-id]] (models.reactions/message-reactions current-public-key - (get-in reactions [(or chat-id current-chat-id) message-id])))) + (get-in reactions [chat-id message-id])))) (re-frame/reg-sub :chats/messages-gaps :<- [:mailserver/gaps] - :<- [:chats/current-chat-id] - (fn [[gaps chat-id]] + (fn [gaps [_ chat-id]] (sort-by :from (vals (get gaps chat-id))))) (re-frame/reg-sub @@ -878,8 +885,7 @@ (re-frame/reg-sub :chats/range :<- [:mailserver/ranges] - :<- [:chats/current-chat-id] - (fn [[ranges chat-id]] + (fn [ranges [_ chat-id]] (get ranges chat-id))) (re-frame/reg-sub @@ -893,21 +899,25 @@ (re-frame/reg-sub :chats/all-loaded? :<- [::pagination-info] - :<- [:chats/current-chat-id] - (fn [[pagination-info chat-id]] + (fn [pagination-info [_ chat-id]] (get-in pagination-info [chat-id :all-loaded?]))) +(re-frame/reg-sub + :chats/loading-messages? + :<- [::pagination-info] + (fn [pagination-info [_ chat-id]] + (get-in pagination-info [chat-id :loading-messages?]))) + (re-frame/reg-sub :chats/public? - :<- [:chats/current-raw-chat] - (fn [chat] - (:public? chat))) + :<- [::chats] + (fn [chats [_ chat-id]] + (get-in chats [chat-id :public?]))) (re-frame/reg-sub :chats/message-list :<- [::message-lists] - :<- [:chats/current-chat-id] - (fn [[message-lists chat-id]] + (fn [message-lists [_ chat-id]] (get message-lists chat-id))) (defn hydrate-messages @@ -920,21 +930,23 @@ message-list)) (re-frame/reg-sub - :chats/current-chat-no-messages? - :<- [:chats/current-chat-messages] + :chats/chat-no-messages? + (fn [[_ chat-id] _] + (re-frame/subscribe [:chats/chat-messages chat-id])) (fn [messages] (empty? messages))) (re-frame/reg-sub - :chats/current-chat-messages-stream - :<- [:chats/message-list] - :<- [:chats/current-chat-messages] - :<- [:chats/messages-gaps] - :<- [:chats/range] - :<- [:chats/all-loaded?] - :<- [:chats/public?] + :chats/chat-messages-stream + (fn [[_ chat-id] _] + [(re-frame/subscribe [:chats/message-list chat-id]) + (re-frame/subscribe [:chats/chat-messages chat-id]) + (re-frame/subscribe [:chats/messages-gaps chat-id]) + (re-frame/subscribe [:chats/range chat-id]) + (re-frame/subscribe [:chats/all-loaded? chat-id]) + (re-frame/subscribe [:chats/public? chat-id])]) (fn [[message-list messages messages-gaps range all-loaded? public?]] - ;;TODO (perf) we need to move all these to status-go + ;;TODO (perf) (-> (models.message-list/->seq message-list) (chat.db/add-datemarks) (hydrate-messages messages) @@ -942,13 +954,17 @@ (re-frame/reg-sub :chats/timeline-messages-stream - :<- [:chats/message-list] - :<- [:chats/current-chat-messages] - :<- [:chats/current-raw-chat] - (fn [[message-list messages {:keys [timeline?]}]] - (when timeline? - (-> (models.message-list/->seq message-list) - (hydrate-messages messages))))) + :<- [:chats/message-list constants/timeline-chat-id] + :<- [:chats/chat-messages constants/timeline-chat-id] + (fn [[message-list messages]] + (-> (models.message-list/->seq message-list) + (hydrate-messages messages)))) + +(re-frame/reg-sub + :chats/current-profile-chat + :<- [:contacts/current-contact-identity] + (fn [identity] + (chat.models/profile-chat-topic identity))) (re-frame/reg-sub :chats/photo-path @@ -997,6 +1013,12 @@ (fn [{:keys [metadata]}] (:sending-image metadata))) +(re-frame/reg-sub + :chats/timeline-sending-image + :<- [:chats/timeline-chat-input] + (fn [{:keys [metadata]}] + (:sending-image metadata))) + (re-frame/reg-sub :public-chat.new/topic-error-message :<- [:public-group-topic] @@ -1035,7 +1057,7 @@ (re-frame/reg-sub :group-chat/inviter-info (fn [[_ chat-id] _] - [(re-frame/subscribe [::chat chat-id]) + [(re-frame/subscribe [:chat-by-id chat-id]) (re-frame/subscribe [:multiaccount/public-key])]) (fn [[chat my-public-key]] {:joined? (group-chats.db/joined? my-public-key chat) @@ -2028,9 +2050,8 @@ (re-frame/reg-sub :chats/fetching-gap-in-progress? - :<- [:chats/current-chat-id] :<- [:mailserver/fetching-gaps-in-progress] - (fn [[chat-id gaps] [_ ids]] + (fn [gaps [_ ids chat-id]] (seq (select-keys (get gaps chat-id) ids)))) (re-frame/reg-sub diff --git a/src/status_im/transport/message/core.cljs b/src/status_im/transport/message/core.cljs index d14fc6a12c..c4b4a8fd68 100644 --- a/src/status_im/transport/message/core.cljs +++ b/src/status_im/transport/message/core.cljs @@ -7,44 +7,19 @@ [status-im.communities.core :as models.communities] [status-im.pairing.core :as models.pairing] [status-im.transport.filters.core :as models.filters] - [status-im.data-store.messages :as data-store.messages] [status-im.data-store.reactions :as data-store.reactions] [status-im.data-store.contacts :as data-store.contacts] [status-im.data-store.chats :as data-store.chats] [status-im.data-store.invitations :as data-store.invitations] [status-im.group-chats.core :as models.group] [status-im.utils.fx :as fx] - [status-im.utils.types :as types])) - -(fx/defn handle-chats [cofx chats] - (models.chat/ensure-chats cofx chats)) - -(fx/defn handle-contacts [cofx contacts] - (models.contact/ensure-contacts cofx contacts)) - -(fx/defn handle-message [cofx message] - (models.message/receive-one cofx message)) - -(fx/defn handle-community [cofx community] - (models.communities/handle-community cofx community)) - -(fx/defn handle-request-to-join-community [cofx request] - (models.communities/handle-request-to-join cofx request)) - -(fx/defn handle-reactions [cofx reactions] - (models.reactions/receive-signal cofx reactions)) - -(fx/defn handle-invitations [cofx invitations] - (models.group/handle-invitations cofx invitations)) - -(fx/defn handle-filters [cofx filters] - (models.filters/handle-filters cofx filters)) - -(fx/defn handle-filters-removed [cofx filters] - (models.filters/handle-filters-removed cofx filters)) + [status-im.utils.types :as types] + [status-im.constants :as constants] + [status-im.multiaccounts.model :as multiaccounts.model] + [clojure.string :as string])) (fx/defn process-response - {:events [::process]} + {:events [:process-response]} [cofx ^js response-js] (let [^js communities (.-communities response-js) ^js requests-to-join-community (.-requestsToJoinCommunity response-js) @@ -56,76 +31,142 @@ ^js filters (.-filters response-js) ^js removed-filters (.-removedFilters response-js) ^js invitations (.-invitations response-js)] + (cond + + (seq chats) + (do + (js-delete response-js "chats") + (fx/merge cofx + {:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]} + (models.chat/ensure-chats (map #(-> % + (data-store.chats/<-rpc) + ;;TODO why here? + (dissoc :unviewed-messages-count)) + (types/js->clj chats))))) + + (seq messages) + (models.message/receive-many cofx response-js) + (seq installations) (let [installations-clj (types/js->clj installations)] (js-delete response-js "installations") (fx/merge cofx - {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} + {:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]} (models.pairing/handle-installations installations-clj))) (seq contacts) (let [contacts-clj (types/js->clj contacts)] (js-delete response-js "contacts") (fx/merge cofx - {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} - (handle-contacts (map data-store.contacts/<-rpc contacts-clj)))) + {:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]} + (models.contact/ensure-contacts (map data-store.contacts/<-rpc contacts-clj)))) (seq communities) (let [community (.pop communities)] (fx/merge cofx - {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} - (handle-community (types/js->clj community)))) + {:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]} + (models.communities/handle-community (types/js->clj community)))) (seq requests-to-join-community) (let [request (.pop requests-to-join-community)] (fx/merge cofx {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} - (handle-request-to-join-community (types/js->clj request)))) - (seq chats) - (let [chats-clj (types/js->clj chats)] - (js-delete response-js "chats") - (fx/merge cofx - {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} - (handle-chats (map #(-> % - (data-store.chats/<-rpc) - (dissoc :unviewed-messages-count)) - chats-clj)))) - - (seq messages) - (let [message (.pop messages)] - (fx/merge cofx - {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} - (handle-message (-> message (types/js->clj) (data-store.messages/<-rpc))))) + (models.communities/handle-request-to-join (types/js->clj request)))) (seq emoji-reactions) (let [reactions (types/js->clj emoji-reactions)] (js-delete response-js "emojiReactions") (fx/merge cofx - {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} - (handle-reactions (map data-store.reactions/<-rpc reactions)))) + {:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]} + (models.reactions/receive-signal (map data-store.reactions/<-rpc reactions)))) (seq invitations) (let [invitations (types/js->clj invitations)] (js-delete response-js "invitations") (fx/merge cofx - {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} - (handle-invitations (map data-store.invitations/<-rpc invitations)))) + {:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]} + (models.group/handle-invitations (map data-store.invitations/<-rpc invitations)))) (seq filters) (let [filters (types/js->clj filters)] (js-delete response-js "filters") (fx/merge cofx - {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} - (handle-filters filters))) + {:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]} + (models.filters/handle-filters filters))) (seq removed-filters) (let [removed-filters (types/js->clj removed-filters)] (js-delete response-js "removedFilters") (fx/merge cofx - {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} - (handle-filters-removed filters)))))) + {:utils/dispatch-later [{:ms 20 :dispatch [:process-response response-js]}]} + (models.filters/handle-filters-removed filters)))))) + +(defn group-by-and-update-unviewed-counts + "group messages by current chat, profile updates, transactions and update unviewed counters in db for not curent chats" + [{:keys [current-chat-id db] :as acc} ^js message-js] + (let [chat-id (.-localChatId message-js) + message-type (.-messageType message-js) + from (.-from message-js) + new (.-new message-js) + current (= current-chat-id chat-id) + profile (models.chat/profile-chat? {:db db} chat-id) + tx-hash (and (.-commandParameters message-js) (.-commandParameters.transactionHash message-js))] + (cond-> acc + current + (update :messages conj message-js) + + profile + (update :statuses conj message-js) + + ;;update counter + (and (not current) + new + (not profile) + (not (= message-type constants/message-type-private-group-system-message)) + (not (= from (multiaccounts.model/current-public-key {:db db})))) + (update-in [:db :chats chat-id :unviewed-messages-count] inc) + + ;;conj incoming transaction for :watch-tx + (not (string/blank? tx-hash)) + (update :transactions conj tx-hash) + + :always + (update :chats conj chat-id)))) + +(defn sort-js-messages! + "sort messages, so we can start process latest first,in that case we only need to process frist 20 and drop others" + [response-js messages] + (if (seq messages) + (set! (.-messages response-js) + (.sort (to-array messages) + (fn [a b] + (- (.-clock b) (.-clock a))))) + (js-delete response-js "messages"))) + +(fx/defn sanitize-messages-and-process-response + "before processing we want to filter and sort messages, so we can process first only messages which will be showed" + {:events [:sanitize-messages-and-process-response]} + [{:keys [db] :as cofx} ^js response-js] + (let [current-chat-id (:current-chat-id db) + {:keys [db messages transactions chats statuses]} + (reduce group-by-and-update-unviewed-counts + {:db db :chats #{} :transactions #{} :statuses [] :messages [] + :current-chat-id current-chat-id} + (.-messages response-js))] + (sort-js-messages! response-js messages) + (fx/merge cofx + {:db db + :utils/dispatch-later (concat [] + (when (seq statuses) + [{:ms 100 :dispatch [:process-statuses statuses]}]) + (when (seq transactions) + (for [transaction-hash transactions] + {:ms 100 :dispatch [:watch-tx transaction-hash]})) + (when (seq chats) + [{:ms 100 :dispatch [:chat/join-times-messages-checked chats]}]))} + (process-response response-js)))) (fx/defn remove-hash - [{:keys [db] :as cofx} envelope-hash] + [{:keys [db]} envelope-hash] {:db (update db :transport/message-envelopes dissoc envelope-hash)}) (fx/defn check-confirmations @@ -166,7 +207,7 @@ (fx/defn set-message-envelope-hash "message-type is used for tracking" - [{:keys [db] :as cofx} chat-id message-id message-type messages-count] + [{:keys [db] :as cofx} chat-id message-id message-type] ;; Check first if the confirmation has already arrived (let [statuses (get-in db [:transport/message-confirmations message-id]) check-confirmations-fx (map @@ -180,5 +221,15 @@ {:chat-id chat-id :message-type message-type}) (update-in [:transport/message-ids->confirmations message-id] - #(or % {:pending-confirmations messages-count})))})] + #(or % {:pending-confirmations 1})))})] (apply fx/merge cofx (conj check-confirmations-fx add-envelope-data)))) + +(fx/defn transport-message-sent + {:events [:transport/message-sent]} + [cofx response-js] + (let [set-hash-fxs (map (fn [{:keys [localChatId id messageType]}] + (set-message-envelope-hash localChatId id messageType)) + (types/js->clj (.-messages response-js)))] + (apply fx/merge cofx + (conj set-hash-fxs + #(sanitize-messages-and-process-response % response-js))))) diff --git a/src/status_im/transport/message/protocol.cljs b/src/status_im/transport/message/protocol.cljs index d7f882ce2b..108d502e85 100644 --- a/src/status_im/transport/message/protocol.cljs +++ b/src/status_im/transport/message/protocol.cljs @@ -26,26 +26,25 @@ :sticker sticker :contentType content-type}) -(fx/defn send-chat-messages [cofx messages] - {::json-rpc/call - [{:method (json-rpc/call-ext-method "sendChatMessages") - :params [(mapv build-message messages)] - :on-success - #(re-frame/dispatch [:transport/message-sent % 1]) - :on-failure #(log/error "failed to send a message" %)}]}) +(fx/defn send-chat-messages [_ messages] + {::json-rpc/call [{:method (json-rpc/call-ext-method "sendChatMessages") + :params [(mapv build-message messages)] + :js-response true + :on-success #(re-frame/dispatch [:transport/message-sent %]) + :on-failure #(log/error "failed to send a message" %)}]}) -(fx/defn send-reaction [cofx {:keys [message-id chat-id emoji-id]}] +(fx/defn send-reaction [_ {:keys [message-id chat-id emoji-id]}] {::json-rpc/call [{:method (json-rpc/call-ext-method "sendEmojiReaction") :params [chat-id message-id emoji-id] - :on-success - #(re-frame/dispatch [:transport/reaction-sent %]) + :js-response true + :on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %]) :on-failure #(log/error "failed to send a reaction" %)}]}) -(fx/defn send-retract-reaction [cofx {:keys [emoji-reaction-id] :as reaction}] +(fx/defn send-retract-reaction [_ {:keys [emoji-reaction-id] :as reaction}] {::json-rpc/call [{:method (json-rpc/call-ext-method "sendEmojiReactionRetraction") :params [emoji-reaction-id] - :on-success - #(re-frame/dispatch [:transport/retraction-sent %]) + :js-response true + :on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %]) :on-failure #(log/error "failed to send a reaction retraction" %)}]}) diff --git a/src/status_im/ui/components/bottom_panel/views.cljs b/src/status_im/ui/components/bottom_panel/views.cljs index 2aac1d1148..b4062ccaea 100644 --- a/src/status_im/ui/components/bottom_panel/views.cljs +++ b/src/status_im/ui/components/bottom_panel/views.cljs @@ -49,47 +49,47 @@ update? (atom nil) current-obj (reagent/atom nil)] (reagent/create-class - {:component-will-mount (fn [args] - (let [[_ obj _ _] (.-argv (.-props args))] - (when @clear-timeout (js/clearTimeout @clear-timeout)) - (when (or (not= obj @current-obj) @update?) - (cond - @update? - (do (reset! update? false) - (show-panel-anim bottom-anim-value alpha-value)) + {:UNSAFE_componentWillMount (fn [args] + (let [[_ obj _ _] (.-argv (.-props args))] + (when @clear-timeout (js/clearTimeout @clear-timeout)) + (when (or (not= obj @current-obj) @update?) + (cond + @update? + (do (reset! update? false) + (show-panel-anim bottom-anim-value alpha-value)) - (and @current-obj obj) - (do (reset! update? true) - (js/setTimeout #(reset! current-obj obj) 600) - (hide-panel-anim bottom-anim-value alpha-value (- window-height))) + (and @current-obj obj) + (do (reset! update? true) + (js/setTimeout #(reset! current-obj obj) 600) + (hide-panel-anim bottom-anim-value alpha-value (- window-height))) - obj - (do (reset! current-obj obj) - (show-panel-anim bottom-anim-value alpha-value)) + obj + (do (reset! current-obj obj) + (show-panel-anim bottom-anim-value alpha-value)) - :else - (do (reset! clear-timeout (js/setTimeout #(reset! current-obj nil) 600)) - (hide-panel-anim bottom-anim-value alpha-value (- window-height))))))) - :component-will-update (fn [_ [_ obj _ _]] - (when @clear-timeout (js/clearTimeout @clear-timeout)) - (when (or (not= obj @current-obj) @update?) - (cond - @update? - (do (reset! update? false) - (show-panel-anim bottom-anim-value alpha-value)) + :else + (do (reset! clear-timeout (js/setTimeout #(reset! current-obj nil) 600)) + (hide-panel-anim bottom-anim-value alpha-value (- window-height))))))) + :UNSAFE_componentWillUpdate (fn [_ [_ obj _ _]] + (when @clear-timeout (js/clearTimeout @clear-timeout)) + (when (or (not= obj @current-obj) @update?) + (cond + @update? + (do (reset! update? false) + (show-panel-anim bottom-anim-value alpha-value)) - (and @current-obj obj) - (do (reset! update? true) - (js/setTimeout #(reset! current-obj obj) 600) - (hide-panel-anim bottom-anim-value alpha-value (- window-height))) + (and @current-obj obj) + (do (reset! update? true) + (js/setTimeout #(reset! current-obj obj) 600) + (hide-panel-anim bottom-anim-value alpha-value (- window-height))) - obj - (do (reset! current-obj obj) - (show-panel-anim bottom-anim-value alpha-value)) + obj + (do (reset! current-obj obj) + (show-panel-anim bottom-anim-value alpha-value)) - :else - (do (reset! clear-timeout (js/setTimeout #(reset! current-obj nil) 600)) - (hide-panel-anim bottom-anim-value alpha-value (- window-height)))))) + :else + (do (reset! clear-timeout (js/setTimeout #(reset! current-obj nil) 600)) + (hide-panel-anim bottom-anim-value alpha-value (- window-height)))))) :reagent-render (fn [] (when @current-obj [react/keyboard-avoiding-view {:style {:position :absolute :top 0 :bottom 0 :left 0 :right 0}} diff --git a/src/status_im/ui/components/connectivity/view.cljs b/src/status_im/ui/components/connectivity/view.cljs index bc3f2ddaed..29fc961578 100644 --- a/src/status_im/ui/components/connectivity/view.cljs +++ b/src/status_im/ui/components/connectivity/view.cljs @@ -1,6 +1,5 @@ (ns status-im.ui.components.connectivity.view (:require [re-frame.core :as re-frame] - [reagent.core :as reagent] [status-im.i18n.i18n :as i18n] [status-im.ui.components.animation :as animation] [status-im.ui.components.colors :as colors] @@ -67,7 +66,7 @@ (defview loading-indicator [] (letsubs [ui-status-properties [:connectivity/ui-status-properties] - window-width (reagent/atom 0)] + window-width [:dimensions/window-width]] (when (:loading-indicator? ui-status-properties) [loading-indicator-anim @window-width]))) diff --git a/src/status_im/ui/components/tabbar/core.cljs b/src/status_im/ui/components/tabbar/core.cljs index c3c653d621..78e8544f41 100644 --- a/src/status_im/ui/components/tabbar/core.cljs +++ b/src/status_im/ui/components/tabbar/core.cljs @@ -79,7 +79,7 @@ (def tabs (reagent/adapt-react-class (fn [props] - (let [{:keys [navigate index route state popToTop]} (bean props) + (let [{:keys [navigate index route popToTop]} (bean props) {:keys [keyboard-shown] :or {keyboard-shown false}} (when platform/android? (rn/use-keyboard)) {:keys [bottom]} (safe-area/use-safe-area) @@ -105,10 +105,7 @@ :label title :on-press #(if (= (str index) (str route-index)) (popToTop) - (let [view-id (navigation/get-index-route-name route-index (bean state))] - (re-frame/dispatch-sync [:screens/tab-will-change view-id]) - (reagent/flush) - (navigate (name nav-stack)))) + (navigate (name nav-stack))) :accessibility-label accessibility-label :count-subscription count-subscription :active? (= (str index) (str route-index)) diff --git a/src/status_im/ui/screens/browser/permissions/views.cljs b/src/status_im/ui/screens/browser/permissions/views.cljs index 4820407710..b6fb5af549 100644 --- a/src/status_im/ui/screens/browser/permissions/views.cljs +++ b/src/status_im/ui/screens/browser/permissions/views.cljs @@ -40,41 +40,41 @@ alpha-value (anim/create-value 0) current-permission (reagent/atom nil) update? (reagent/atom nil)] - {:component-will-update (fn [_ [_ _ {:keys [requested-permission]}]] - (cond - @update? - ;; the component has been updated with a new permission, we show the panel - (do (reset! update? false) - (show-panel-anim bottom-anim-value alpha-value)) + {:UNSAFE_componentWillUpdate (fn [_ [_ _ {:keys [requested-permission]}]] + (cond + @update? + ;; the component has been updated with a new permission, we show the panel + (do (reset! update? false) + (show-panel-anim bottom-anim-value alpha-value)) - (and @current-permission requested-permission) - ;; a permission has been accepted/denied by the user, and there is - ;; another permission that needs to be processed by the user - ;; we hide the processed permission with an animation and update - ;; `current-permission` with a delay so that the information is still - ;; available during the animation - (do (reset! update? true) - (js/setTimeout #(reset! current-permission - (permission-details requested-permission)) - 600) - (hide-panel-anim bottom-anim-value alpha-value)) + (and @current-permission requested-permission) + ;; a permission has been accepted/denied by the user, and there is + ;; another permission that needs to be processed by the user + ;; we hide the processed permission with an animation and update + ;; `current-permission` with a delay so that the information is still + ;; available during the animation + (do (reset! update? true) + (js/setTimeout #(reset! current-permission + (permission-details requested-permission)) + 600) + (hide-panel-anim bottom-anim-value alpha-value)) - requested-permission - ;; the dapp is asking for a permission, we put it in current-permission - ;; and start the show-animation - (do (reset! current-permission - (get browser.permissions/supported-permissions - requested-permission)) - (show-panel-anim bottom-anim-value alpha-value)) + requested-permission + ;; the dapp is asking for a permission, we put it in current-permission + ;; and start the show-animation + (do (reset! current-permission + (get browser.permissions/supported-permissions + requested-permission)) + (show-panel-anim bottom-anim-value alpha-value)) - :else - ;; a permission has been accepted/denied by the user, and there is - ;; no other permission that needs to be processed by the user - ;; we hide the processed permission with an animation and update - ;; `current-permission` with a delay so that the information is still - ;; available during the animation - (do (js/setTimeout #(reset! current-permission nil) 500) - (hide-panel-anim bottom-anim-value alpha-value))))} + :else + ;; a permission has been accepted/denied by the user, and there is + ;; no other permission that needs to be processed by the user + ;; we hide the processed permission with an animation and update + ;; `current-permission` with a delay so that the information is still + ;; available during the animation + (do (js/setTimeout #(reset! current-permission nil) 500) + (hide-panel-anim bottom-anim-value alpha-value))))} (when @current-permission (let [{:keys [title description type icon]} @current-permission] [react/view styles/permissions-panel-container diff --git a/src/status_im/ui/screens/chat/components/input.cljs b/src/status_im/ui/screens/chat/components/input.cljs index 7d4da10aba..962ebed804 100644 --- a/src/status_im/ui/screens/chat/components/input.cljs +++ b/src/status_im/ui/screens/chat/components/input.cljs @@ -146,37 +146,36 @@ (let [mentionable-users @(re-frame/subscribe [:chats/mentionable-users]) timeout-id (atom nil) last-text-change (atom nil)] - [rn/view {:style (styles/text-input-wrapper)} - [rn/text-input - {:style (styles/text-input) - :ref text-input-ref - :max-font-size-multiplier 1 - :accessibility-label :chat-message-input - :text-align-vertical :center - :multiline true - :editable (not cooldown-enabled?) - :blur-on-submit false - :auto-focus false - :on-focus #(set-active-panel nil) - :max-length chat.constants/max-text-size - :placeholder-text-color (:text-02 @colors/theme) - :placeholder (if cooldown-enabled? - (i18n/label :cooldown/text-input-disabled) - (i18n/label :t/type-a-message)) - :underline-color-android :transparent - :auto-capitalize :sentences - :on-selection-change (partial on-selection-change timeout-id last-text-change mentionable-users) - :on-change (partial on-change - on-text-change last-text-change timeout-id mentionable-users) - :on-text-input (partial on-text-input mentionable-users)} - (for [[idx [type text]] (map-indexed - (fn [idx item] - [idx item]) - input-with-mentions)] - ^{:key (str idx "_" type "_" text)} - [rn/text (when (= type :mention) - {:style {:color "#0DA4C9"}}) - text])]])) + [rn/text-input + {:style (styles/text-input) + :ref text-input-ref + :max-font-size-multiplier 1 + :accessibility-label :chat-message-input + :text-align-vertical :center + :multiline true + :editable (not cooldown-enabled?) + :blur-on-submit false + :auto-focus false + :on-focus #(set-active-panel nil) + :max-length chat.constants/max-text-size + :placeholder-text-color (:text-02 @colors/theme) + :placeholder (if cooldown-enabled? + (i18n/label :cooldown/text-input-disabled) + (i18n/label :t/type-a-message)) + :underline-color-android :transparent + :auto-capitalize :sentences + :on-selection-change (partial on-selection-change timeout-id last-text-change mentionable-users) + :on-change (partial on-change + on-text-change last-text-change timeout-id mentionable-users) + :on-text-input (partial on-text-input mentionable-users)} + (for [[idx [type text]] (map-indexed + (fn [idx item] + [idx item]) + input-with-mentions)] + ^{:key (str idx "_" type "_" text)} + [rn/text (when (= type :mention) + {:style {:color "#0DA4C9"}}) + text])])) (defn mention-item [[public-key {:keys [alias name nickname] :as user}] _ _ text-input-ref] diff --git a/src/status_im/ui/screens/chat/components/style.cljs b/src/status_im/ui/screens/chat/components/style.cljs index bbad22209d..63d32decd9 100644 --- a/src/status_im/ui/screens/chat/components/style.cljs +++ b/src/status_im/ui/screens/chat/components/style.cljs @@ -38,6 +38,8 @@ (merge typography/font-regular typography/base {:flex 1 + :min-height 34 + :max-height 144 :margin 0 :border-width 0 :flex-shrink 1 diff --git a/src/status_im/ui/screens/chat/message/gap.cljs b/src/status_im/ui/screens/chat/message/gap.cljs index 0d3b84994b..35f4856e49 100644 --- a/src/status_im/ui/screens/chat/message/gap.cljs +++ b/src/status_im/ui/screens/chat/message/gap.cljs @@ -7,7 +7,7 @@ [status-im.ui.screens.chat.styles.input.gap :as style])) (defn on-press - [ids first-gap? idx list-ref] + [ids first-gap? idx list-ref chat-id] (fn [] (when (and list-ref @list-ref) (.scrollToIndex ^js @list-ref @@ -15,24 +15,25 @@ :viewOffset 20 :viewPosition 0.5})) (if first-gap? - (re-frame/dispatch [:chat.ui/fetch-more]) - (re-frame/dispatch [:chat.ui/fill-gaps ids])))) + (re-frame/dispatch [:chat.ui/fetch-more chat-id]) + (re-frame/dispatch [:chat.ui/fill-gaps ids chat-id])))) (views/defview gap - [{:keys [gaps first-gap?]} idx list-ref timeline] - (views/letsubs [range [:chats/range] - {:keys [might-have-join-time-messages?]} [:chats/current-raw-chat] + [{:keys [gaps first-gap?]} idx list-ref timeline chat-id] + (views/letsubs [range [:chats/range chat-id] + {:keys [might-have-join-time-messages?]} [:chat-by-id chat-id] in-progress? [:chats/fetching-gap-in-progress? (if first-gap? [:first-gap] - (:ids gaps))] + (:ids gaps)) + chat-id] connected? [:mailserver/connected?]] (let [ids (:ids gaps)] (when-not (and first-gap? might-have-join-time-messages?) [react/view {:style style/gap-container} [react/touchable-highlight {:on-press (when (and connected? (not in-progress?)) - (on-press ids first-gap? idx list-ref)) + (on-press ids first-gap? idx list-ref chat-id)) :style style/touchable} [react/view {:style style/label-container} (if in-progress? diff --git a/src/status_im/ui/screens/chat/message/message.cljs b/src/status_im/ui/screens/chat/message/message.cljs index d2d1ce844c..8c320d38a1 100644 --- a/src/status_im/ui/screens/chat/message/message.cljs +++ b/src/status_im/ui/screens/chat/message/message.cljs @@ -120,7 +120,7 @@ outgoing colors/mention-outgoing :else colors/mention-incoming)} :on-press (when-not (= content-type constants/content-type-system-text) - #(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact literal]))} + #(re-frame/dispatch [:chat.ui/show-profile literal]))} [mention-element literal]]) "status-tag" (conj acc [react/text-class @@ -286,17 +286,16 @@ [react/view (style/message-author-userpic outgoing) (when first-in-group? [react/touchable-highlight {:on-press #(do (when modal (close-modal)) - (re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))} + (re-frame/dispatch [:chat.ui/show-profile from]))} [photos/member-photo from identicon]])]) [react/view {:style (style/message-author-wrapper outgoing display-photo?)} (when display-username? [react/touchable-opacity {:style style/message-author-touchable :on-press #(do (when modal (close-modal)) - (re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))} + (re-frame/dispatch [:chat.ui/show-profile from]))} [message-author-name from {:modal modal}]]) ;;MESSAGE CONTENT - [react/view - content] + content [link-preview/link-preview-wrapper (:links (:content message)) outgoing false]]] ; delivery status [react/view (style/delivery-status outgoing) @@ -475,7 +474,7 @@ (on-long-press (when-not outgoing [{:on-press #(when pack - (re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from])) + (re-frame/dispatch [:chat.ui/show-profile from])) :label (i18n/label :t/view-details)}])))}) [react/image {:style {:margin 10 :width 140 :height 140} ;;TODO (perf) move to event @@ -512,7 +511,7 @@ (defn chat-message [message space-keeper] [reactions/with-reaction-picker {:message message - :reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message)]) + :reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)]) :picker-on-open (fn [] (space-keeper true)) :picker-on-close (fn [] diff --git a/src/status_im/ui/screens/chat/message/reactions.cljs b/src/status_im/ui/screens/chat/message/reactions.cljs index 6ea4176665..f2d12bc921 100644 --- a/src/status_im/ui/screens/chat/message/reactions.cljs +++ b/src/status_im/ui/screens/chat/message/reactions.cljs @@ -62,35 +62,35 @@ (reset! position pos) (reset! visible true))] [:<> - [animated/view {:style {:opacity (animated/mix animation 1 0)}} - [rn/view {:ref ref - :collapsable false} - [render message {:modal false - :on-long-press (fn [act] - (when (or (not outgoing) - (and outgoing (= outgoing-status :sent))) - (reset! actions act) - (get-picker-position ref on-open)))}]] + [rn/view {:ref ref + :style {:opacity (if @visible 0 1)} + :collapsable false} + [render message {:modal false + :on-long-press (fn [act] + (when (or (not outgoing) + (and outgoing (= outgoing-status :sent))) + (reset! actions act) + (get-picker-position ref on-open)))}] [reaction-row/message-reactions message reactions timeline]] - [rn/modal {:visible @visible - :on-request-close on-close - :on-show (fn [] - (js/requestAnimationFrame - #(animated/set-value animated-state 1))) - :transparent true} - [reaction-picker/modal {:outgoing (:outgoing message) - :display-photo (:display-photo? message) - :animation animation - :spring spring-animation - :top (:top @position) - :message-height (:height @position) - :on-close on-close - :actions @actions - :own-reactions own-reactions - :timeline timeline - :send-emoji (fn [emoji] - (on-close) - (js/setTimeout #(on-emoji-press emoji) - reaction-picker/animation-duration))} - [render message {:modal true - :close-modal on-close}]]]])))) + (when @visible + [rn/modal {:on-request-close on-close + :on-show (fn [] + (js/requestAnimationFrame + #(animated/set-value animated-state 1))) + :transparent true} + [reaction-picker/modal {:outgoing (:outgoing message) + :display-photo (:display-photo? message) + :animation animation + :spring spring-animation + :top (:top @position) + :message-height (:height @position) + :on-close on-close + :actions @actions + :own-reactions own-reactions + :timeline timeline + :send-emoji (fn [emoji] + (on-close) + (js/setTimeout #(on-emoji-press emoji) + reaction-picker/animation-duration))} + [render message {:modal true + :close-modal on-close}]]])])))) diff --git a/src/status_im/ui/screens/chat/state.cljs b/src/status_im/ui/screens/chat/state.cljs index 094bde4d87..081641b4f2 100644 --- a/src/status_im/ui/screens/chat/state.cljs +++ b/src/status_im/ui/screens/chat/state.cljs @@ -2,5 +2,7 @@ (defonce first-not-visible-item (atom nil)) -(defn reset [] +(defonce scrolling (atom nil)) + +(defn reset-visible-item [] (reset! first-not-visible-item nil)) diff --git a/src/status_im/ui/screens/chat/stickers/views.cljs b/src/status_im/ui/screens/chat/stickers/views.cljs index 8bb4d7eb17..b4c73ac94e 100644 --- a/src/status_im/ui/screens/chat/stickers/views.cljs +++ b/src/status_im/ui/screens/chat/stickers/views.cljs @@ -82,8 +82,8 @@ (defview stickers-paging-panel [installed-packs selected-pack] (letsubs [ref (atom nil) width [:dimensions/window-width]] - {:component-will-update (fn [_ [_ installed-packs selected-pack]] - (update-scroll-position @ref installed-packs selected-pack width true)) + {:UNSAFE_componentWillUpdate (fn [_ [_ installed-packs selected-pack]] + (update-scroll-position @ref installed-packs selected-pack width true)) :component-did-mount #(update-scroll-position @ref installed-packs selected-pack width false)} [react/scroll-view {:style {:flex 1} :horizontal true diff --git a/src/status_im/ui/screens/chat/toolbar_content.cljs b/src/status_im/ui/screens/chat/toolbar_content.cljs index 4288521b01..e881d2b003 100644 --- a/src/status_im/ui/screens/chat/toolbar_content.cljs +++ b/src/status_im/ui/screens/chat/toolbar_content.cljs @@ -39,18 +39,19 @@ chat-type chat-name public?]}] - [react/view {:style st/toolbar-container} - [react/view {:margin-right 10} - [chat-icon.screen/chat-icon-view-toolbar chat-id group-chat chat-name color]] - [react/view {:style st/chat-name-view} - (if group-chat - [react/text {:style st/chat-name-text - :number-of-lines 1 - :accessibility-label :chat-name-text} - chat-name] - [one-to-one-name chat-id]) - (when-not group-chat - [contact-indicator chat-id]) - (when (and group-chat (not invitation-admin) (not= chat-type constants/community-chat-type)) - [group-last-activity {:contacts contacts - :public? public?}])]]) + (when chat-id + [react/view {:style st/toolbar-container} + [react/view {:margin-right 10} + [chat-icon.screen/chat-icon-view-toolbar chat-id group-chat chat-name color]] + [react/view {:style st/chat-name-view} + (if group-chat + [react/text {:style st/chat-name-text + :number-of-lines 1 + :accessibility-label :chat-name-text} + chat-name] + [one-to-one-name chat-id]) + (when-not group-chat + [contact-indicator chat-id]) + (when (and group-chat (not invitation-admin) (not= chat-type constants/community-chat-type)) + [group-last-activity {:contacts contacts + :public? public?}])]])) diff --git a/src/status_im/ui/screens/chat/views.cljs b/src/status_im/ui/screens/chat/views.cljs index 90a93164b8..73b44b7ed3 100644 --- a/src/status_im/ui/screens/chat/views.cljs +++ b/src/status_im/ui/screens/chat/views.cljs @@ -11,7 +11,6 @@ [status-im.ui.screens.chat.sheets :as sheets] [quo.animated :as animated] [quo.react-native :as rn] - [quo.platform :as platform] [status-im.ui.screens.chat.audio-message.views :as audio-message] [quo.react :as quo.react] [status-im.ui.screens.chat.message.message :as message] @@ -20,7 +19,6 @@ [status-im.ui.screens.chat.toolbar-content :as toolbar-content] [status-im.ui.screens.chat.image.views :as image] [status-im.ui.screens.chat.state :as state] - [status-im.utils.debounce :as debounce] [status-im.ui.screens.chat.extensions.views :as extensions] [status-im.ui.components.topbar :as topbar] [status-im.ui.screens.chat.group :as chat.group] @@ -32,20 +30,20 @@ [status-im.ui.components.toolbar :as toolbar] [quo.core :as quo] [clojure.string :as string] - [status-im.constants :as constants])) + [status-im.constants :as constants] + [status-im.utils.platform :as platform] + [status-im.utils.utils :as utils])) -(defn topbar [] - (let [current-chat @(re-frame/subscribe [:current-chat/metadata])] - [topbar/topbar - {:content [toolbar-content/toolbar-content-view current-chat] - :navigation {:on-press #(re-frame/dispatch [:navigate-to :home])} - :right-accessories [{:icon :main-icons/more - :accessibility-label :chat-menu-button - :on-press - #(re-frame/dispatch [:bottom-sheet/show-sheet - {:content (fn [] - [sheets/actions current-chat]) - :height 256}])}]}])) +(defn topbar [current-chat] + [topbar/topbar + {:content [toolbar-content/toolbar-content-view current-chat] + :navigation {:on-press #(re-frame/dispatch [:close-chat (:chat-id current-chat)])} + :right-accessories [{:icon :main-icons/more + :accessibility-label :chat-menu-button + :on-press #(re-frame/dispatch [:bottom-sheet/show-sheet + {:content (fn [] + [sheets/actions current-chat]) + :height 256}])}]}]) (defn invitation-requests [chat-id admins] (let [current-pk @(re-frame/subscribe [:multiaccount/public-key]) @@ -153,60 +151,7 @@ first-not-visible (aget (.-data ^js (.-props ^js @messages-list-ref)) (inc index))] (when (and first-not-visible (= :message (:type first-not-visible))) - first-not-visible))))) - (debounce/debounce-and-dispatch [:chat.ui/message-visibility-changed e] 5000)) - -(defn render-fn [{:keys [outgoing type] :as message} idx _ {:keys [group-chat public? current-public-key space-keeper]}] - [react/view {:style (when platform/android? {:scaleY -1})} - (if (= type :datemark) - [message-datemark/chat-datemark (:value message)] - (if (= type :gap) - [gap/gap message idx messages-list-ref false] - ; message content - [message/chat-message - (assoc message - :incoming-group (and group-chat (not outgoing)) - :group-chat group-chat - :public? public? - :current-public-key current-public-key) - space-keeper]))]) - -(defn messages-view - [{:keys [chat bottom-space pan-responder space-keeper]}] - (let [{:keys [group-chat chat-id chat-type public? invitation-admin]} chat - - messages @(re-frame/subscribe [:chats/current-chat-messages-stream]) - no-messages? @(re-frame/subscribe [:chats/current-chat-no-messages?]) - current-public-key @(re-frame/subscribe [:multiaccount/public-key])] - [list/flat-list - (merge - pan-responder - {:key-fn #(or (:message-id %) (:value %)) - :ref #(reset! messages-list-ref %) - :header (when (= chat-type constants/private-group-chat-type) - [react/view {:style (when platform/android? {:scaleY -1})} - [chat.group/group-chat-footer chat-id invitation-admin]]) - :footer [react/view {:style (when platform/android? {:scaleY -1})} - [chat-intro-header-container chat no-messages?] - (when (= chat-type constants/one-to-one-chat-type) - [invite.chat/reward-messages])] - :data messages - ;;TODO https://github.com/facebook/react-native/issues/30034 - :inverted (when platform/ios? true) - :style (when platform/android? {:scaleY -1}) - :render-data {:group-chat group-chat - :public? public? - :current-public-key current-public-key - :space-keeper space-keeper} - :render-fn render-fn - :on-viewable-items-changed on-viewable-items-changed - :on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages]) - :on-scroll-to-index-failed #() ;;don't remove this - :content-container-style {:padding-top (+ bottom-space 16) - :padding-bottom 16} - :scrollIndicatorInsets {:top bottom-space} - :keyboardDismissMode "interactive" - :keyboard-should-persist-taps :handled})])) + first-not-visible)))))) (defn bottom-sheet [input-bottom-sheet] (case input-bottom-sheet @@ -258,72 +203,155 @@ :on-press #(re-frame/dispatch [:send-group-chat-membership-request])} (i18n/label :t/request-membership)]}])])) +(defn get-space-keeper-ios [bottom-space panel-space active-panel text-input-ref] + (fn [state] + ;; NOTE: Only iOs now because we use soft input resize screen on android + (when platform/ios? + (cond + (and state + (< @bottom-space @panel-space) + (not @active-panel)) + (reset! bottom-space @panel-space) + + (and (not state) + (< @panel-space @bottom-space)) + (do + (some-> ^js (quo.react/current-ref text-input-ref) .focus) + (reset! panel-space @bottom-space) + (reset! bottom-space 0)))))) + +(defn get-set-active-panel [active-panel] + (fn [panel] + (rn/configure-next + (:ease-opacity-200 rn/custom-animations)) + (reset! active-panel panel) + (reagent/flush) + (when panel + (js/setTimeout #(react/dismiss-keyboard!) 100)))) + +(defn list-footer [{:keys [chat-id chat-type] :as chat}] + (let [loading-messages? @(re-frame/subscribe [:chats/loading-messages? chat-id]) + no-messages? @(re-frame/subscribe [:chats/chat-no-messages? chat-id]) + all-loaded? @(re-frame/subscribe [:chats/all-loaded? chat-id])] + [react/view {:style (when platform/android? {:scaleY -1})} + (if (or loading-messages? (not chat-id) (not all-loaded?)) + [react/view {:height 324 :align-items :center :justify-content :center} + [react/activity-indicator {:animating true}]] + [chat-intro-header-container chat no-messages?]) + (when (= chat-type constants/one-to-one-chat-type) + [invite.chat/reward-messages])])) + +(defn list-header [{:keys [chat-id chat-type invitation-admin]}] + (when (= chat-type constants/private-group-chat-type) + [react/view {:style (when platform/android? {:scaleY -1})} + [chat.group/group-chat-footer chat-id invitation-admin]])) + +(defn render-fn [{:keys [outgoing type] :as message} + idx + _ + {:keys [group-chat public? current-public-key space-keeper chat-id]}] + [react/view {:style (when platform/android? {:scaleY -1})} + (if (= type :datemark) + [message-datemark/chat-datemark (:value message)] + (if (= type :gap) + [gap/gap message idx messages-list-ref false chat-id] + ; message content + [message/chat-message + (assoc message + :incoming-group (and group-chat (not outgoing)) + :group-chat group-chat + :public? public? + :current-public-key current-public-key) + space-keeper]))]) + +(defn messages-view + [{:keys [chat bottom-space pan-responder space-keeper]}] + (let [{:keys [group-chat chat-id public?]} chat + messages @(re-frame/subscribe [:chats/chat-messages-stream chat-id]) + current-public-key @(re-frame/subscribe [:multiaccount/public-key])] + [list/flat-list + (merge + pan-responder + {:key-fn #(or (:message-id %) (:value %)) + :ref #(reset! messages-list-ref %) + :header [list-header chat] + :footer [list-footer chat] + :data messages + :render-data {:group-chat group-chat + :public? public? + :current-public-key current-public-key + :space-keeper space-keeper + :chat-id chat-id} + :render-fn render-fn + :on-viewable-items-changed on-viewable-items-changed + ;;TODO this is not really working in pair with inserting new messages because we stop inserting new messages + ;;if they outside the viewarea, but we load more here because end is reached,so its slowdown UI because we + ;;load and render 20 messages more, but we can't prevent this , because otherwise :on-end-reached will work wrong + :on-end-reached (fn [] + (if @state/scrolling + (re-frame/dispatch [:chat.ui/load-more-messages chat-id]) + (utils/set-timeout #(re-frame/dispatch [:chat.ui/load-more-messages chat-id]) + (if platform/low-device? 500 200)))) + + :on-scroll-to-index-failed #() ;;don't remove this + :content-container-style {:padding-top (+ bottom-space 16) + :padding-bottom 16} + :scroll-indicator-insets {:top bottom-space} ;;ios only + :keyboard-dismiss-mode :interactive + :keyboard-should-persist-taps :handled + :onMomentumScrollBegin #(reset! state/scrolling true) + :onMomentumScrollEnd #(reset! state/scrolling false) + ;;TODO https://github.com/facebook/react-native/issues/30034 + :inverted (when platform/ios? true) + :style (when platform/android? {:scaleY -1})})])) + (defn chat [] (let [bottom-space (reagent/atom 0) - panel-space (reagent/atom 0) + panel-space (reagent/atom 52) active-panel (reagent/atom nil) position-y (animated/value 0) pan-state (animated/value 0) text-input-ref (quo.react/create-ref) - on-update (partial reset! panel-space) + on-update #(when-not (zero? %) (reset! panel-space %)) pan-responder (accessory/create-pan-responder position-y pan-state) - space-keeper (fn [state] - ;; NOTE: Only iOs now because we use soft input resize screen on android - (when platform/ios? - (cond - (and state - (< @bottom-space @panel-space) - (not @active-panel)) - (reset! bottom-space @panel-space) - - (and (not state) - (< @panel-space @bottom-space)) - (do - (some-> ^js (quo.react/current-ref text-input-ref) .focus) - (reset! panel-space @bottom-space) - (reset! bottom-space 0))))) - set-active-panel (fn [panel] - (rn/configure-next - (:ease-opacity-200 rn/custom-animations)) - (reset! active-panel panel) - (reagent/flush) - (when panel - (js/setTimeout #(react/dismiss-keyboard!) 100))) + space-keeper (get-space-keeper-ios bottom-space panel-space active-panel text-input-ref) + set-active-panel (get-set-active-panel active-panel) on-text-change #(re-frame/dispatch [:chat.ui/set-chat-input-text %])] (fn [] (let [{:keys [chat-id show-input? group-chat admins invitation-admin] :as current-chat} @(re-frame/subscribe [:chats/current-chat])] - (when current-chat - [react/view {:style {:flex 1}} - [connectivity/loading-indicator] - [topbar] - [react/view {:style {:flex 1}} + [:<> + [connectivity/loading-indicator] + [topbar current-chat] + [:<> + (when current-chat (if group-chat [invitation-requests chat-id admins] - [add-contact-bar chat-id]) - [messages-view {:chat current-chat - :bottom-space (max @bottom-space @panel-space) - :pan-responder pan-responder - :space-keeper space-keeper}]] - (when (and group-chat invitation-admin) - [accessory/view {:y position-y - :on-update-inset on-update} - [invitation-bar chat-id]]) - ;; NOTE(rasom): on android we have to place `autocomplete-mentions` - ;; outside `accessory/view` because otherwise :keyboardShouldPersistTaps - ;; :always doesn't work and keyboard is hidden on pressing suggestion. - ;; Scrolling of suggestions doesn't work neither in this case. - (when platform/android? - [components/autocomplete-mentions text-input-ref]) - (when show-input? - [accessory/view {:y position-y - :pan-state pan-state - :has-panel (boolean @active-panel) - :on-close #(set-active-panel nil) - :on-update-inset on-update} - [components/chat-toolbar - {:active-panel @active-panel - :set-active-panel set-active-panel - :text-input-ref text-input-ref - :on-text-change on-text-change}] - [bottom-sheet @active-panel]])]))))) + [add-contact-bar chat-id])) + ;;MESSAGES LIST + [messages-view {:chat current-chat + :bottom-space (max @bottom-space @panel-space) + :pan-responder pan-responder + :space-keeper space-keeper}]] + (when (and group-chat invitation-admin) + [accessory/view {:y position-y + :on-update-inset on-update} + [invitation-bar chat-id]]) + ;; NOTE(rasom): on android we have to place `autocomplete-mentions` + ;; outside `accessory/view` because otherwise :keyboardShouldPersistTaps + ;; :always doesn't work and keyboard is hidden on pressing suggestion. + ;; Scrolling of suggestions doesn't work neither in this case. + (when platform/android? + [components/autocomplete-mentions text-input-ref]) + (when show-input? + [accessory/view {:y position-y + :pan-state pan-state + :has-panel (boolean @active-panel) + :on-close #(set-active-panel nil) + :on-update-inset on-update} + [components/chat-toolbar + {:active-panel @active-panel + :set-active-panel set-active-panel + :text-input-ref text-input-ref + :on-text-change on-text-change}] + [bottom-sheet @active-panel]])])))) diff --git a/src/status_im/ui/screens/home/views.cljs b/src/status_im/ui/screens/home/views.cljs index ee86693344..9ba629279e 100644 --- a/src/status_im/ui/screens/home/views.cljs +++ b/src/status_im/ui/screens/home/views.cljs @@ -169,9 +169,8 @@ (when (or (seq items) @search-active? (seq search-filter)) [search-input-wrapper search-filter items]) [referral-item/list-item] - (when - (and (empty? items) - (or @search-active? (seq search-filter))) + (when (and (empty? items) + (or @search-active? (seq search-filter))) [start-suggestion search-filter])] :footer (if (and (not hide-home-tooltip?) (not @search-active?)) [home-tooltip-view] @@ -194,9 +193,9 @@ (defn home [] [react/keyboard-avoiding-view {:style styles/home-container} - [topbar/topbar {:title (i18n/label :t/chat) - :navigation :none - :right-component [connectivity/connectivity-button]}] + [topbar/topbar {:title (i18n/label :t/chat) + :navigation :none + :right-component [connectivity/connectivity-button]}] [connectivity/loading-indicator] [chats-list] [plus-button]]) diff --git a/src/status_im/ui/screens/home/views/inner_item.cljs b/src/status_im/ui/screens/home/views/inner_item.cljs index f4804b47e1..3a0ae5c637 100644 --- a/src/status_im/ui/screens/home/views/inner_item.cljs +++ b/src/status_im/ui/screens/home/views/inner_item.cljs @@ -174,13 +174,10 @@ [message-content-text {:content (:content last-message) :content-type (:content-type last-message)}]] [unviewed-indicator home-item]] - :on-press #(do - (re-frame/dispatch [:dismiss-keyboard]) - (re-frame/dispatch [:chat.ui/navigate-to-chat chat-id]) - (re-frame/dispatch [:search/home-filter-changed nil]) - (if public? - (re-frame/dispatch [:chat.ui/mark-public-all-read chat-id]) - (re-frame/dispatch [:chat.ui/mark-messages-seen :chat]))) + :on-press (fn [] + (re-frame/dispatch [:dismiss-keyboard]) + (re-frame/dispatch [:chat.ui/navigate-to-chat chat-id]) + (re-frame/dispatch [:search/home-filter-changed nil])) :on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet {:content (fn [] [sheets/actions home-item])}])}])) diff --git a/src/status_im/ui/screens/popover/views.cljs b/src/status_im/ui/screens/popover/views.cljs index f8162df03a..7a6c8bb12c 100644 --- a/src/status_im/ui/screens/popover/views.cljs +++ b/src/status_im/ui/screens/popover/views.cljs @@ -69,7 +69,7 @@ "hardwareBackPress" request-close)))] (reagent/create-class - {:component-will-update + {:UNSAFE_componentWillUpdate (fn [_ [_ popover _]] (when @clear-timeout (js/clearTimeout @clear-timeout)) (cond diff --git a/src/status_im/ui/screens/profile/contact/views.cljs b/src/status_im/ui/screens/profile/contact/views.cljs index dfb0ec866b..7bd806bf60 100644 --- a/src/status_im/ui/screens/profile/contact/views.cljs +++ b/src/status_im/ui/screens/profile/contact/views.cljs @@ -20,8 +20,7 @@ [clojure.string :as string] [quo.components.list.item :as list-item] [status-im.ui.components.list.views :as list] - [status-im.ui.screens.status.views :as status.views] - [status-im.ui.screens.chat.views :as chat.views]) + [status-im.ui.screens.status.views :as status.views]) (:require-macros [status-im.utils.views :as views])) (defn actions @@ -156,60 +155,54 @@ :number-of-lines 2} label]]]) -(defn status [] - (let [messages @(re-frame/subscribe [:chats/current-chat-messages-stream]) - no-messages? @(re-frame/subscribe [:chats/current-chat-no-messages?]) - {:keys [profile-public-key]} @(re-frame/subscribe [:chats/current-raw-chat])] - (when profile-public-key - [list/flat-list - {:key-fn #(or (:message-id %) (:value %)) - :header (when no-messages? - [react/view {:padding-horizontal 32 :margin-top 32} - [react/view (styles/updates-descr-cont) - [react/text {:style {:color colors/gray :line-height 22}} - (i18n/label :t/status-updates-descr)]]]) - :ref #(reset! status.views/messages-list-ref %) - :on-viewable-items-changed chat.views/on-viewable-items-changed - :on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages]) - :on-scroll-to-index-failed #() ;;don't remove this - :render-fn status.views/render-message - :data messages}]))) - -(views/defview profile [] - (views/letsubs [{:keys [public-key name ens-verified] - :as contact} [:contacts/current-contact]] - (let [muted? (:muted @(re-frame/subscribe [:chats/chat public-key])) - [first-name second-name] (multiaccounts/contact-two-names contact true) - on-share #(re-frame/dispatch [:show-popover (merge - {:view :share-chat-key - :address public-key} - (when (and ens-verified name) - {:ens-name name}))])] - (when contact - [react/view {:flex 1} - [quo/animated-header - {:use-insets false - :right-accessories [{:icon :main-icons/share - :accessibility-label :share-button - :on-press on-share}] - :left-accessories [{:icon :main-icons/close - :accessibility-label :back-button - :on-press #(re-frame/dispatch [:navigate-back])}] - :extended-header (profile-header/extended-header - {:on-press on-share - :bottom-separator false - :title first-name - :photo (multiaccounts/displayed-photo contact) - :monospace (not ens-verified) - :subtitle second-name})} - [react/view {:height 1 :background-color colors/gray-lighter :margin-top 8}] - [nickname-settings contact] - [react/view {:height 1 :background-color colors/gray-lighter}] - [react/view {:padding-top 17 :flex-direction :row :align-items :stretch :flex 1} - (for [{:keys [label] :as action} (actions contact muted?) - :when label] - ^{:key label} - [button-item action])] - [react/view {:height 1 :background-color colors/gray-lighter :margin-top 16}] - [status]]])))) +(defn profile [] + (let [{:keys [public-key name ens-verified] :as contact} @(re-frame/subscribe [:contacts/current-contact]) + current-chat-id @(re-frame/subscribe [:chats/current-profile-chat]) + messages @(re-frame/subscribe [:chats/chat-messages-stream current-chat-id]) + no-messages? @(re-frame/subscribe [:chats/chat-no-messages? current-chat-id]) + muted? (:muted @(re-frame/subscribe [:chats/chat public-key])) + [first-name second-name] (multiaccounts/contact-two-names contact true) + on-share #(re-frame/dispatch [:show-popover (merge + {:view :share-chat-key + :address public-key} + (when (and ens-verified name) + {:ens-name name}))])] + (when contact + [:<> + [quo/header {:right-accessories [{:icon :main-icons/share + :accessibility-label :share-button + :on-press on-share}] + :left-accessories [{:icon :main-icons/close + :accessibility-label :back-button + :on-press #(re-frame/dispatch [:navigate-back])}]}] + [list/flat-list + {:key-fn #(or (:message-id %) (:value %)) + :header [:<> + [(profile-header/extended-header + {:on-press on-share + :bottom-separator false + :title first-name + :photo (multiaccounts/displayed-photo contact) + :monospace (not ens-verified) + :subtitle second-name})] + [react/view {:height 1 :background-color colors/gray-lighter :margin-top 8}] + [nickname-settings contact] + [react/view {:height 1 :background-color colors/gray-lighter}] + [react/view {:padding-top 17 :flex-direction :row :align-items :stretch :flex 1} + (for [{:keys [label] :as action} (actions contact muted?) + :when label] + ^{:key label} + [button-item action])] + [react/view {:height 1 :background-color colors/gray-lighter :margin-top 16}] + (when no-messages? + [react/view {:padding-horizontal 32 :margin-top 32} + [react/view (styles/updates-descr-cont) + [react/text {:style {:color colors/gray :line-height 22}} + (i18n/label :t/status-updates-descr)]]])] + :ref #(reset! status.views/messages-list-ref %) + :on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages current-chat-id]) + :on-scroll-to-index-failed #() ;;don't remove this + :render-data {:chat-id current-chat-id} + :render-fn status.views/render-message + :data messages}]]))) diff --git a/src/status_im/ui/screens/routing/chat_stack.cljs b/src/status_im/ui/screens/routing/chat_stack.cljs index 32657aaaf0..c5c221e2ec 100644 --- a/src/status_im/ui/screens/routing/chat_stack.cljs +++ b/src/status_im/ui/screens/routing/chat_stack.cljs @@ -25,8 +25,8 @@ (defonce communities-stack (navigation/create-stack)) (defn chat-stack [] - [stack {:initial-route-name :home - :header-mode :none} + [stack {:initial-route-name :home + :header-mode :none} [{:name :home :style {:padding-bottom tabbar.styles/tabs-diff} :component home/home} diff --git a/src/status_im/ui/screens/routing/status_stack.cljs b/src/status_im/ui/screens/routing/status_stack.cljs index b00d488613..166e7df009 100644 --- a/src/status_im/ui/screens/routing/status_stack.cljs +++ b/src/status_im/ui/screens/routing/status_stack.cljs @@ -9,6 +9,7 @@ [stack {:initial-route-name :status :header-mode :none} [{:name :status + :on-focus [:init-timeline-chat] :insets {:top true} :style {:padding-bottom tabbar.styles/tabs-diff} - :component status.views/timeline}]]) \ No newline at end of file + :component status.views/timeline}]]) diff --git a/src/status_im/ui/screens/status/new/views.cljs b/src/status_im/ui/screens/status/new/views.cljs index 139504f8de..0f207755f6 100644 --- a/src/status_im/ui/screens/status/new/views.cljs +++ b/src/status_im/ui/screens/status/new/views.cljs @@ -19,16 +19,16 @@ [react/view styles/buttons [pressable/pressable {:type :scale :accessibility-label :take-picture - :on-press #(re-frame/dispatch [:chat.ui/show-image-picker-camera])} + :on-press #(re-frame/dispatch [:chat.ui/show-image-picker-camera-timeline])} [icons/icon :main-icons/camera]] [react/view {:style {:padding-top 8}} - [pressable/pressable {:on-press #(re-frame/dispatch [:chat.ui/open-image-picker]) + [pressable/pressable {:on-press #(re-frame/dispatch [:chat.ui/open-image-picker-timeline]) :accessibility-label :open-gallery :type :scale} [icons/icon :main-icons/gallery]]]]) (defn image-preview [uri] - [react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/camera-roll-pick uri])} + [react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/camera-roll-pick-timeline uri])} [react/image {:style styles/image :source {:uri uri}}]]) @@ -52,8 +52,8 @@ scroll (reagent/atom nil) autoscroll? (reagent/atom false) scroll-height (reagent/atom nil) - input-text (re-frame/subscribe [:chats/current-chat-input-text]) - sending-image (re-frame/subscribe [:chats/sending-image])] + input-text (re-frame/subscribe [:chats/timeline-chat-input-text]) + sending-image (re-frame/subscribe [:chats/timeline-sending-image])] (fn [] (let [{:keys [uri]} (first (vals @sending-image))] [kb-presentation/keyboard-avoiding-view {:style {:flex 1}} @@ -83,7 +83,7 @@ @scroll-height -40)] (.scrollTo @scroll #js {:y height :animated true}))) - :on-change-text #(re-frame/dispatch [:chat.ui/set-chat-input-text %]) + :on-change-text #(re-frame/dispatch [:chat.ui/set-timeline-input-text %]) :default-value @input-text :placeholder (i18n/label :t/whats-on-your-mind)}] (when uri diff --git a/src/status_im/ui/screens/status/views.cljs b/src/status_im/ui/screens/status/views.cljs index 76f051c0f4..3d71d7123d 100644 --- a/src/status_im/ui/screens/status/views.cljs +++ b/src/status_im/ui/screens/status/views.cljs @@ -11,7 +11,6 @@ [status-im.ui.components.list.views :as list] [status-im.i18n.i18n :as i18n] [status-im.ui.screens.status.styles :as styles] - [status-im.ui.screens.chat.views :as chat.views] [status-im.ui.components.plus-button :as components.plus-button] [status-im.ui.screens.chat.image.preview.views :as preview] [status-im.ui.screens.chat.photos :as photos] @@ -65,7 +64,7 @@ :background-color :transparent :border-color colors/black-transparent}] (when show-close? - [react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/cancel-sending-image]) + [react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/cancel-sending-image-timeline]) :accessibility-label :cancel-send-image :style {:right 4 :top 12 :position :absolute}} [react/view {:width 24 @@ -110,7 +109,7 @@ {:border-radius 16})) [react/touchable-highlight {:on-press #(do (when modal (close-modal)) - (re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))} + (re-frame/dispatch [:chat.ui/show-profile from]))} [react/view {:padding-top 2 :padding-right 8} (if outgoing [photos/account-photo account] @@ -120,7 +119,7 @@ :justify-content :space-between} [react/touchable-highlight {:on-press #(do (when modal (close-modal)) - (re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))} + (re-frame/dispatch [:chat.ui/show-profile from]))} (if outgoing [message/message-my-name {:profile? true :you? false}] [message/message-author-name from {:profile? true}])] @@ -134,14 +133,14 @@ [message/render-parsed-text (assoc message :outgoing false) (:parsed-text content)]]) [link-preview/link-preview-wrapper (:links content) outgoing true]]]])) -(defn render-message [{:keys [type] :as message} idx _ {:keys [timeline account]}] +(defn render-message [{:keys [type] :as message} idx _ {:keys [timeline account chat-id]}] (if (= type :datemark) nil (if (= type :gap) (if timeline nil - [gap/gap message idx messages-list-ref true]) - ; message content + [gap/gap message idx messages-list-ref true chat-id]) + ;; for timeline for reactions we need to use :from as chat-id (let [chat-id (chat/profile-chat-topic (:from message))] [react/view (merge {:accessibility-label :chat-item} (when (:last-in-group? message) @@ -152,7 +151,7 @@ [reactions/with-reaction-picker {:message message :timeline true - :reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message)]) + :reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) constants/timeline-chat-id]) :picker-on-open (fn []) :picker-on-close (fn []) :send-emoji (fn [{:keys [emoji-id]}] @@ -182,37 +181,38 @@ (defn timeline [] (let [messages @(re-frame/subscribe [:chats/timeline-messages-stream]) - no-messages? @(re-frame/subscribe [:chats/current-chat-no-messages?]) + loading-messages? @(re-frame/subscribe [:chats/loading-messages? constants/timeline-chat-id]) + no-messages? @(re-frame/subscribe [:chats/chat-no-messages? constants/timeline-chat-id]) account @(re-frame/subscribe [:multiaccount])] [react/view {:flex 1} - ;;TODO implement in the next iteration - #_[tabs] [react/view {:height 1 :background-color colors/gray-lighter}] - (if no-messages? - [react/view {:padding-horizontal 32 - :margin-top 64} - [react/image {:style {:width 140 - :height 140 - :align-self :center} - :source {:uri (contenthash/url image-hash)}}] - [react/view (styles/descr-container) - [react/text {:style {:color colors/gray - :line-height 22}} - (if (= :timeline (:tab @state)) - (i18n/label :t/statuses-descr) - (i18n/label :t/statuses-my-status-descr))]]] - [list/flat-list - {:key-fn #(or (:message-id %) (:value %)) - :render-data {:timeline (= :timeline (:tab @state)) - :account account} - :render-fn render-message - :data messages - :on-viewable-items-changed chat.views/on-viewable-items-changed - :on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages]) - ;;don't remove :on-scroll-to-index-failed - :on-scroll-to-index-failed #() - :header [react/view {:height 8}] - :footer [react/view {:height 68}]}]) + (if loading-messages? + [react/view {:flex 1 :align-items :center :justify-content :center} + [react/activity-indicator {:animating true}]] + (if no-messages? + [react/view {:padding-horizontal 32 + :margin-top 64} + [react/image {:style {:width 140 + :height 140 + :align-self :center} + :source {:uri (contenthash/url image-hash)}}] + [react/view (styles/descr-container) + [react/text {:style {:color colors/gray + :line-height 22}} + (if (= :timeline (:tab @state)) + (i18n/label :t/statuses-descr) + (i18n/label :t/statuses-my-status-descr))]]] + [list/flat-list + {:key-fn #(or (:message-id %) (:value %)) + :render-data {:timeline (= :timeline (:tab @state)) + :account account} + :render-fn render-message + :data messages + :on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages constants/timeline-chat-id]) + ;;don't remove :on-scroll-to-index-failed + :on-scroll-to-index-failed #() + :header [react/view {:height 8}] + :footer [react/view {:height 68}]}])) [components.plus-button/plus-button {:on-press #(re-frame/dispatch [:navigate-to :my-status])}]])) diff --git a/src/status_im/utils/platform.cljs b/src/status_im/utils/platform.cljs index 1040b162ab..b79a58327a 100644 --- a/src/status_im/utils/platform.cljs +++ b/src/status_im/utils/platform.cljs @@ -33,3 +33,5 @@ (defn android-version>= [v] (and android? (>= version v))) + +(def low-device? (and android? (< version 29))) diff --git a/src/status_im/utils/types.cljs b/src/status_im/utils/types.cljs index d446ac2f52..adac5536e7 100644 --- a/src/status_im/utils/types.cljs +++ b/src/status_im/utils/types.cljs @@ -23,6 +23,13 @@ (catch js/Error _ (when (string? json) json))))) +(defn json->js [json] + (when-not (= json "undefined") + (try + (.parse js/JSON json) + (catch js/Error _ + (when (string? json) json))))) + (def serialize clj->json) (defn deserialize [o] (try (json->clj o) (catch :default _ nil))) diff --git a/src/status_im/utils/utils.cljs b/src/status_im/utils/utils.cljs index 29e84cc885..dfc585e8f6 100644 --- a/src/status_im/utils/utils.cljs +++ b/src/status_im/utils/utils.cljs @@ -98,7 +98,8 @@ :utils/dispatch-later (fn [params] (doseq [{:keys [ms dispatch]} params] - (set-timeout #(re-frame/dispatch dispatch) ms)))) + (when (and ms dispatch) + (set-timeout #(re-frame/dispatch dispatch) ms))))) (defn clear-timeout [id] (.clearTimeout background-timer id)) diff --git a/src/status_im/wallet/core.cljs b/src/status_im/wallet/core.cljs index 2d4dfdea23..05b87687e0 100644 --- a/src/status_im/wallet/core.cljs +++ b/src/status_im/wallet/core.cljs @@ -383,7 +383,8 @@ (when-not (= symbol :ETH) address) from-address] - :on-success #(re-frame/dispatch [:transport/message-sent % 1])}]}))) + :js-response true + :on-success #(re-frame/dispatch [:transport/message-sent %])}]}))) (fx/defn accept-request-transaction-button-clicked-from-command {:events [:wallet.ui/accept-request-transaction-button-clicked-from-command]}