From 52ddccca968094f6a34b55520ae99adfd09c8c7d Mon Sep 17 00:00:00 2001 From: janherich Date: Sat, 23 Dec 2017 21:27:04 +0100 Subject: [PATCH] Refactored statuses --- src/status_im/chat/events.cljs | 47 +++-- src/status_im/chat/events/sign_up.cljs | 11 +- src/status_im/chat/handlers/send_message.cljs | 11 +- src/status_im/chat/models/message.cljs | 39 +++-- .../chat/models/unviewed_messages.cljs | 13 -- src/status_im/chat/specs.cljs | 4 +- src/status_im/chat/subs.cljs | 8 +- src/status_im/chat/utils.cljs | 3 + .../chat/views/input/suggestions.cljs | 73 ++++---- src/status_im/chat/views/message/message.cljs | 102 +++++------ src/status_im/data_store/messages.cljs | 60 +++---- .../data_store/pending_messages.cljs | 4 +- src/status_im/data_store/realm/messages.cljs | 23 ++- .../data_store/realm/pending_messages.cljs | 5 +- .../realm/schemas/account/v19/core.cljs | 23 ++- .../realm/schemas/account/v19/message.cljs | 4 +- .../schemas/account/v19/user_status.cljs | 7 + src/status_im/protocol/handlers.cljs | 165 ++++++------------ src/status_im/ui/screens/db.cljs | 5 +- 19 files changed, 287 insertions(+), 320 deletions(-) delete mode 100644 src/status_im/chat/models/unviewed_messages.cljs create mode 100644 src/status_im/data_store/realm/schemas/account/v19/user_status.cljs diff --git a/src/status_im/chat/events.cljs b/src/status_im/chat/events.cljs index 4549385320..f5020b7316 100644 --- a/src/status_im/chat/events.cljs +++ b/src/status_im/chat/events.cljs @@ -4,7 +4,6 @@ [status-im.utils.handlers :as handlers] [status-im.utils.gfycat.core :as gfycat] [status-im.chat.models :as model] - [status-im.chat.models.unviewed-messages :as unviewed-messages-model] [status-im.chat.console :as console-chat] [status-im.chat.constants :as chat-const] [status-im.data-store.messages :as msg-store] @@ -31,7 +30,8 @@ (re-frame/reg-cofx :stored-unviewed-messages (fn [cofx _] - (assoc cofx :stored-unviewed-messages (messages-store/get-unviewed)))) + (assoc cofx :stored-unviewed-messages + (msg-store/get-unviewed (-> cofx :db :current-public-key))))) (re-frame/reg-cofx :get-stored-message @@ -170,16 +170,16 @@ (if account-creation? {:db db :dispatch load-default-contacts-event} - (let [chat->unviewed-messages (unviewed-messages-model/index-unviewed-messages stored-unviewed-messages) - chat->message-id->request (reduce (fn [acc {:keys [chat-id message-id] :as request}] + (let [chat->message-id->request (reduce (fn [acc {:keys [chat-id message-id] :as request}] (assoc-in acc [chat-id message-id] request)) {} stored-unanswered-requests) chats (reduce (fn [acc {:keys [chat-id] :as chat}] - (assoc acc chat-id (assoc chat - :unviewed-messages (get chat->unviewed-messages chat-id) - :requests (get chat->message-id->request chat-id) - :messages (index-messages (get-stored-messages chat-id))))) + (assoc acc chat-id + (assoc chat + :unviewed-messages (get stored-unviewed-messages chat-id) + :requests (get chat->message-id->request chat-id) + :messages (index-messages (get-stored-messages chat-id))))) {} all-stored-chats)] (-> db @@ -190,23 +190,22 @@ (handlers/register-handler-fx :send-seen! [re-frame/trim-v] - (fn [{:keys [db]} [{:keys [message-id chat-id from]}]] - (let [{:keys [web3 current-public-key chats] - :contacts/keys [contacts]} db - {:keys [group-chat public?]} (get chats chat-id)] + (fn [{:keys [db]} [{:keys [chat-id from me message-id]}]] + (let [{:keys [web3 chats] :contacts/keys [contacts]} db + {:keys [group-chat public? messages]} (get chats chat-id) + statuses (assoc (get-in messages [message-id :user-statuses]) me :seen)] (cond-> {:db (-> db - (unviewed-messages-model/remove-unviewed-message chat-id message-id) - (assoc-in [:chats chat-id :messages message-id :message-status] :seen)) - :update-message {:message-id message-id - :message-status :seen}} - (and (not (get-in contacts [chat-id] :dapp?)) - (not public?)) - (assoc :protocol-send-seen - {:web3 web3 - :message (cond-> {:from current-public-key - :to from - :message-id message-id} - group-chat (assoc :group-id chat-id))}))))) + (update-in [:chats chat-id :unviewed-messages] disj message-id) + (assoc-in [:chats chat-id :messages message-id :user-statuses] statuses)) + :update-message {:message-id message-id + :user-statuses statuses}} + ;; for public chats and 1-1 bot/dapp chats, it makes no sense to signalise `:seen` msg + (not (or public? (get-in contacts [chat-id] :dapp?))) + (assoc :protocol-send-seen {:web3 web3 + :message (cond-> {:from me + :to from + :message-id message-id} + group-chat (assoc :group-id chat-id))}))))) (handlers/register-handler-fx :show-mnemonic diff --git a/src/status_im/chat/events/sign_up.cljs b/src/status_im/chat/events/sign_up.cljs index cadac62a9e..b010e5cdbf 100644 --- a/src/status_im/chat/events/sign_up.cljs +++ b/src/status_im/chat/events/sign_up.cljs @@ -44,10 +44,13 @@ (sign-up db phone-number message-id))) (defn- message-seen [{:keys [db] :as fx} message-id] - (-> fx - (assoc-in [:db :chats const/console-chat-id :messages message-id :message-status] :seen) - (assoc :update-message {:message-id message-id - :message-status :seen}))) + (let [statuses-path [:chats const/console-chat-id :messages message-id :user-statuses] + statuses (-> (get-in db statuses-path) + (assoc const/console-chat-id :seen))] + (-> fx + (assoc-in (into [:db] statuses-path) statuses) + (assoc :update-message {:message-id message-id + :user-statuses statuses})))) (handlers/register-handler-fx :start-listening-confirmation-code-sms diff --git a/src/status_im/chat/handlers/send_message.cljs b/src/status_im/chat/handlers/send_message.cljs index e67a962b2a..803ac49589 100644 --- a/src/status_im/chat/handlers/send_message.cljs +++ b/src/status_im/chat/handlers/send_message.cljs @@ -79,9 +79,15 @@ (dispatch [:prepare-command! chat-id params]))) (dispatch [:set-chat-ui-props {:sending-in-progress? false}])))) +(defn- message-type [{:keys [group-chat public?]}] + (cond + (and group-chat public?) :public-group-user-message + (and group-chat (not public?)) :group-user-message + :else :user-message)) + (register-handler :prepare-command! (u/side-effect! - (fn [{:keys [current-public-key network-status] :as db} + (fn [{:keys [current-public-key network-status chats] :as db} [_ add-to-chat-id {{:keys [handler-data command] :as content} :command @@ -92,7 +98,8 @@ hidden-params (->> (:params command) (filter :hidden) (map :name)) - command' (prepare-command current-public-key chat-id clock-value request content)] + command' (-> (prepare-command current-public-key chat-id clock-value request content) + (assoc :message-type (message-type (get chats chat-id))))] (dispatch [:update-message-overhead! chat-id network-status]) (dispatch [:set-chat-ui-props {:sending-in-progress? false}]) (dispatch [::send-command! add-to-chat-id (assoc params :command command') hidden-params]) diff --git a/src/status_im/chat/models/message.cljs b/src/status_im/chat/models/message.cljs index 6f7b444aa3..c2a5b8f784 100644 --- a/src/status_im/chat/models/message.cljs +++ b/src/status_im/chat/models/message.cljs @@ -4,8 +4,7 @@ [status-im.constants :as constants] [status-im.chat.utils :as chat-utils] [status-im.chat.models :as chat-model] - [status-im.chat.models.commands :as commands-model] - [status-im.chat.models.unviewed-messages :as unviewed-messages-model] + [status-im.chat.models.commands :as commands-model] [status-im.chat.events.requests :as requests-events] [taoensso.timbre :as log])) @@ -33,8 +32,8 @@ (defn- add-message-to-db [db {:keys [message-id] :as message} chat-id] (-> db - (chat-utils/add-message-to-db chat-id chat-id message (:new? message)) - (unviewed-messages-model/add-unviewed-message chat-id message-id))) + (update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id) + (chat-utils/add-message-to-db chat-id chat-id message (:new? message)))) (defn receive [{:keys [db message-exists? pop-up-chat? get-last-clock-value now] :as cofx} @@ -57,21 +56,23 @@ command-request? (= content-type constants/content-type-command-request) command (:command content) enriched-message (cond-> (assoc message - :chat-id chat-identifier - :timestamp (or timestamp now) - :show? true - :clock-value (clocks/receive - clock-value - (get-last-clock-value chat-identifier))) - (and command command-request?) - (assoc-in [:content :content-command-ref] - (lookup-response-ref access-scope->commands-responses - current-account - (get-in fx [:db :chats chat-identifier]) - contacts - command)))] + :chat-id chat-identifier + :timestamp (or timestamp now) + :show? true + :clock-value (clocks/receive + clock-value + (get-last-clock-value chat-identifier))) + public-key + (assoc :user-statuses {public-key :received}) + (and command command-request?) + (assoc-in [:content :content-command-ref] + (lookup-response-ref access-scope->commands-responses + current-account + (get-in fx [:db :chats chat-identifier]) + contacts + command)))] (cond-> (-> fx (update :db add-message-to-db enriched-message chat-identifier) (assoc :save-message (dissoc enriched-message :new?))) - command-request? - (requests-events/add-request chat-identifier enriched-message)))))) \ No newline at end of file + command-request? + (requests-events/add-request chat-identifier enriched-message)))))) diff --git a/src/status_im/chat/models/unviewed_messages.cljs b/src/status_im/chat/models/unviewed_messages.cljs deleted file mode 100644 index 56bf1c12b2..0000000000 --- a/src/status_im/chat/models/unviewed_messages.cljs +++ /dev/null @@ -1,13 +0,0 @@ -(ns status-im.chat.models.unviewed-messages) - -(defn index-unviewed-messages [unviewed-messages] - (into {} - (map (fn [[chat-id messages]] - [chat-id (into #{} (map :message-id) messages)])) - (group-by :chat-id unviewed-messages))) - -(defn add-unviewed-message [db chat-id message-id] - (update-in db [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id)) - -(defn remove-unviewed-message [db chat-id message-id] - (update-in db [:chats chat-id :unviewed-messages] disj message-id)) diff --git a/src/status_im/chat/specs.cljs b/src/status_im/chat/specs.cljs index 8f9d33e2fa..b82b9e5fa0 100644 --- a/src/status_im/chat/specs.cljs +++ b/src/status_im/chat/specs.cljs @@ -10,10 +10,8 @@ (s/def :chat/chat-list-ui-props (s/nilable map?)) (s/def :chat/layout-height (s/nilable number?)) ; height of chat's view layout (s/def :chat/expandable-view-height-to-value (s/nilable number?)) -(s/def :chat/message-status (s/nilable map?)) ; TODO janherich: remove later (s/def :chat/selected-participants (s/nilable set?)) -(s/def :chat/chat-loaded-callbacks (s/nilable map?)) -(s/def :chat/command-hash-valid? (s/nilable boolean?)) +(s/def :chat/chat-loaded-callbacks (s/nilable map?)) (s/def :chat/public-group-topic (s/nilable string?)) (s/def :chat/confirmation-code-sms-listener (s/nilable any?)) ; .addListener result object (s/def :chat/messages (s/nilable map?)) ; messages indexed by message-id diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index df3cd28fa6..f4cbcb0d80 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -61,6 +61,12 @@ (fn [[chats current-chat-id]] (get chats current-chat-id))) +(reg-sub + :get-current-chat-message + :<- [:get-current-chat] + (fn [{:keys [messages]} [_ message-id]] + (get messages message-id))) + (reg-sub :chat :<- [:chats] @@ -265,7 +271,7 @@ (:web-view-extra-js current-chat))) (reg-sub - :photo-path + :get-photo-path :<- [:get-contacts] (fn [contacts [_ id]] (:photo-path (contacts id)))) diff --git a/src/status_im/chat/utils.cljs b/src/status_im/chat/utils.cljs index 95fa743aa5..245ae82dc6 100644 --- a/src/status_im/chat/utils.cljs +++ b/src/status_im/chat/utils.cljs @@ -9,5 +9,8 @@ :new? (if (nil? new?) true new?))] (update-in db [:chats add-to-chat-id :messages] assoc message-id prepared-message)))) +(defn message-seen-by? [message user-pk] + (= :seen (get-in message [:user-statuses user-pk]))) + (defn command-name [{:keys [name]}] (str chat.constants/command-char name)) diff --git a/src/status_im/chat/views/input/suggestions.cljs b/src/status_im/chat/views/input/suggestions.cljs index 08b28fc67d..b2d13726af 100644 --- a/src/status_im/chat/views/input/suggestions.cljs +++ b/src/status_im/chat/views/input/suggestions.cljs @@ -1,72 +1,63 @@ (ns status-im.chat.views.input.suggestions (:require-macros [status-im.utils.views :refer [defview]]) - (:require [re-frame.core :refer [subscribe dispatch]] - [status-im.ui.components.react :refer [view - scroll-view - touchable-highlight - text - icon]] - [status-im.data-store.messages :as messages] + (:require [re-frame.core :as re-frame] + [status-im.ui.components.react :as react] [status-im.chat.styles.input.suggestions :as style] - [status-im.chat.constants :as const] - [status-im.chat.views.input.animations.expandable :refer [expandable-view]] - [status-im.chat.views.input.utils :as input-utils] - [status-im.i18n :refer [label]] - [taoensso.timbre :as log] - [status-im.chat.utils :as chat-utils])) + [status-im.chat.views.input.animations.expandable :as expandable] + [status-im.chat.utils :as chat.utils] + [status-im.i18n :as i18n])) (defn suggestion-item [{:keys [on-press name description last?]}] - [touchable-highlight {:on-press on-press} - [view (style/item-suggestion-container last?) - [view {:style style/item-suggestion-name} - [text {:style style/item-suggestion-name-text - :font :roboto-mono} name]] - [text {:style style/item-suggestion-description - :number-of-lines 2} + [react/touchable-highlight {:on-press on-press} + [react/view (style/item-suggestion-container last?) + [react/view {:style style/item-suggestion-name} + [react/text {:style style/item-suggestion-name-text + :font :roboto-mono} name]] + [react/text {:style style/item-suggestion-description + :number-of-lines 2} description]]]) (defview response-item [{:keys [name description] - {:keys [type message-id]} :request :as command} last?] - [{:keys [chat-id]} [:get-current-chat]] + {:keys [message-id]} :request :as command} last?] + [{{:keys [params]} :content} [:get-current-chat-message message-id]] [suggestion-item - {:on-press #(let [{:keys [params]} (messages/get-message-content-by-id message-id) - metadata (assoc params :to-message-id message-id)] - (dispatch [:select-chat-input-command command metadata])) - :name (chat-utils/command-name command) + {:on-press #(let [metadata (assoc params :to-message-id message-id)] + (re-frame/dispatch [:select-chat-input-command command metadata])) + :name (chat.utils/command-name command) :description description :last? last?}]) (defn command-item [{:keys [name description bot] :as command} last?] [suggestion-item - {:on-press #(dispatch [:select-chat-input-command command nil]) - :name (chat-utils/command-name command) + {:on-press #(re-frame/dispatch [:select-chat-input-command command nil]) + :name (chat.utils/command-name command) :description description :last? last?}]) -(defn item-title [top-padding? s] - [view (style/item-title-container top-padding?) - [text {:style style/item-title-text} - s]]) +(defn item-title [top-padding? title] + [react/view (style/item-title-container top-padding?) + [react/text {:style style/item-title-text} + title]]) (defview suggestions-view [] [show-suggestions? [:show-suggestions?] responses [:get-available-responses] commands [:get-available-commands]] (when show-suggestions? - [expandable-view {:key :suggestions - :draggable? false - :height 212} - [view {:flex 1} - [scroll-view {:keyboardShouldPersistTaps :always} + [expandable/expandable-view {:key :suggestions + :draggable? false + :height 212} + [react/view {:flex 1} + [react/scroll-view {:keyboardShouldPersistTaps :always} (when (seq responses) - [view - [item-title false (label :t/suggestions-requests)] + [react/view + [item-title false (i18n/label :t/suggestions-requests)] (for [[i response] (map-indexed vector responses)] ^{:key i} [response-item response (= i (dec (count responses)))])]) (when (seq commands) - [view - [item-title (seq responses) (label :t/suggestions-commands)] + [react/view + [item-title (seq responses) (i18n/label :t/suggestions-commands)] (for [[i command] (map-indexed vector commands)] ^{:key i} [command-item command (= i (dec (count commands)))])])]]])) diff --git a/src/status_im/chat/views/message/message.cljs b/src/status_im/chat/views/message/message.cljs index 32a73f8a26..60dba77cc3 100644 --- a/src/status_im/chat/views/message/message.cljs +++ b/src/status_im/chat/views/message/message.cljs @@ -174,71 +174,66 @@ [message-content-audio {:content content :content-type content-type}]]]) -(defview group-message-delivery-status [{:keys [message-id group-id message-status user-statuses] :as msg}] - (letsubs [chat [:get-current-chat] - contacts [:get-contacts]] - (let [status (or message-status :sending) - participants (:contacts chat) - seen-by-everyone? (and (= (count user-statuses) (count participants)) - (every? (fn [[_ {:keys [status]}]] - (= (keyword status) :seen)) user-statuses))] - (if (or (zero? (count user-statuses)) - seen-by-everyone?) - [react/view style/delivery-view - [react/text {:style style/delivery-text - :font :default} - (i18n/message-status-label - (if seen-by-everyone? - :seen-by-everyone - status))]] +(defn- text-status [status] + [react/view style/delivery-view + [react/text {:style style/delivery-text + :font :default} + (i18n/message-status-label status)]]) + +(defview group-message-delivery-status [{:keys [message-id group-id current-public-key user-statuses] :as msg}] + (letsubs [{participants :contacts} [:get-current-chat] + contacts [:get-contacts]] + (let [outgoing-status (or (get user-statuses current-public-key) :sending) + delivery-statuses (dissoc user-statuses current-public-key) + delivery-statuses-count (count delivery-statuses) + seen-by-everyone (and (= delivery-statuses-count (count participants)) + (every? (comp (partial = :seen) second) delivery-statuses) + :seen-by-everyone)] + (if (or seen-by-everyone (zero? delivery-statuses-count)) + [text-status (or seen-by-everyone outgoing-status)] [react/touchable-highlight - {:on-press (fn [] - (re-frame/dispatch [:show-message-details {:message-status status - :user-statuses user-statuses - :participants participants}]))} + {:on-press #(re-frame/dispatch [:show-message-details {:message-status outgoing-status + :user-statuses delivery-statuses + :participants participants}])} [react/view style/delivery-view - (for [[_ {:keys [whisper-identity]}] (take 3 user-statuses)] + (for [[whisper-identity] (take 3 delivery-statuses)] ^{:key whisper-identity} [react/image {:source {:uri (or (get-in contacts [whisper-identity :photo-path]) (identicon/identicon whisper-identity))} :style {:width 16 :height 16 :borderRadius 8}}]) - (if (> (count user-statuses) 3) + (if (> delivery-statuses-count 3) [react/text {:style style/delivery-text :font :default} - (str "+ " (- (count user-statuses) 3))])]])))) + (str "+ " (- delivery-statuses-count 3))])]])))) (defn message-delivery-status - [{:keys [message-id chat-id message-status user-statuses content]}] - (let [delivery-status (get-in user-statuses [chat-id :status]) - status (cond (and (not (console/commands-with-delivery-status (:command content))) - (= constants/console-chat-id chat-id)) + [{:keys [message-id chat-id current-public-key user-statuses content]}] + (let [outgoing-status (or (get user-statuses current-public-key) :sending) + delivery-status (get user-statuses chat-id) + status (cond (and (= constants/console-chat-id chat-id) + (not (console/commands-with-delivery-status (:command content)))) :seen :else - (or delivery-status message-status :sending))] - [react/view style/delivery-view - [react/text {:style style/delivery-text - :font :default} - (i18n/message-status-label status)]])) + (or delivery-status outgoing-status))] + [text-status status])) + +(defn- photo [from photo-path] + [react/view + [react/image {:source {:uri (if (string/blank? photo-path) + (identicon/identicon from) + photo-path)} + :style style/photo}]]) (defview member-photo [from] - (letsubs [photo-path [:photo-path from]] - [react/view - [react/image {:source {:uri (if (string/blank? photo-path) - (identicon/identicon from) - photo-path)} - :style style/photo}]])) + (letsubs [photo-path [:get-photo-path from]] + (photo from photo-path))) (defview my-photo [from] - (letsubs [account [:get-current-account]] - (let [{:keys [photo-path]} account] - [react/view - [react/image {:source {:uri (if (string/blank? photo-path) - (identicon/identicon from) - photo-path)} - :style style/photo}]]))) + (letsubs [{:keys [photo-path]} [:get-current-account]] + (photo from photo-path))) (defn message-body [{:keys [last-outgoing? message-type same-author? from outgoing] :as message} content] @@ -291,18 +286,16 @@ children)])})) (into [react/view] children))) -(defn chat-message [{:keys [outgoing message-id chat-id message-status user-statuses - from current-public-key] :as message}] +(defn chat-message [{:keys [outgoing message-id chat-id from current-public-key] :as message}] (reagent/create-class {:display-name "chat-message" :component-did-mount - #(when (and message-id - chat-id - (not outgoing) - (not= :seen message-status) - (not= :seen (keyword (get-in user-statuses [current-public-key :status])))) + ;; send `:seen` signal when we have signed-in user, message not from us and we didn't sent it already + #(when (and current-public-key message-id chat-id (not outgoing) + (not (chat.utils/message-seen-by? message current-public-key))) (re-frame/dispatch [:send-seen! {:chat-id chat-id :from from + :me current-public-key :message-id message-id}])) :reagent-render (fn [{:keys [outgoing group-chat content-type content] :as message}] @@ -321,4 +314,5 @@ [react/view (let [incoming-group (and group-chat (not outgoing))] [message-content message-body (merge message - {:incoming-group incoming-group})])]]])})) + {:current-public-key current-public-key + :incoming-group incoming-group})])]]])})) diff --git a/src/status_im/data_store/messages.cljs b/src/status_im/data_store/messages.cljs index fedff15906..ec2bf2dbf8 100644 --- a/src/status_im/data_store/messages.cljs +++ b/src/status_im/data_store/messages.cljs @@ -14,8 +14,7 @@ (def default-values {:outgoing false - :to nil - :preview nil}) + :to nil}) (defn exists? [message-id] (data-store/exists? message-id)) @@ -24,17 +23,11 @@ [message-id] (data-store/get-by-id message-id)) -(defn get-message-content-by-id [message-id] - (when-let [{:keys [content-type content] :as message} (get-by-id message-id)] - (when (command-type? content-type) - (reader/read-string content)))) - (defn get-by-chat-id ([chat-id] (get-by-chat-id chat-id 0)) ([chat-id from] (->> (data-store/get-by-chat-id chat-id from constants/default-number-of-messages) - reverse (keep (fn [{:keys [content-type preview] :as message}] (if (command-type? content-type) (update message :content reader/read-string) @@ -46,13 +39,6 @@ (filter #(= (:content-type %) constants/content-type-log-message)) (map #(select-keys % [:content :timestamp])))) -(defn get-last-outgoing - [chat-id number-of-messages] - (data-store/get-by-fields {:chat-id chat-id - :outgoing true} - 0 - number-of-messages)) - (defn get-last-clock-value [chat-id] (if-let [message (data-store/get-last-message chat-id)] @@ -60,34 +46,46 @@ 0)) (defn get-unviewed - [] - (data-store/get-unviewed)) + [current-public-key] + (into {} + (map (fn [[chat-id user-statuses]] + [chat-id (into #{} (map :message-id) user-statuses)])) + (group-by :chat-id (data-store/get-unviewed current-public-key)))) (defn- prepare-content [content] (if (string? content) content (pr-str ;; TODO janherich: this is ugly and not systematic, define something like `:not-persisent` - ;; option for command params instead + ;; option for command params instead (update content :params dissoc :password :password-confirmation)))) -(defn save - [{:keys [message-id content] :as message}] +(defn- prepare-statuses [{:keys [chat-id message-id] :as message}] + (utils/update-if-present message + :user-statuses + (partial map (fn [[whisper-identity status]] + {:whisper-identity whisper-identity + :status status + :chat-id chat-id + :message-id message-id})))) + +(defn- prepare-message [message] + (-> message + prepare-statuses + (utils/update-if-present :content prepare-content))) + +(defn save + [{:keys [message-id content from] :as message}] (when-not (data-store/exists? message-id) - (let [content' (prepare-content content) - message' (merge default-values - message - {:content content' - :timestamp (random/timestamp)})] - (data-store/save message')))) + (data-store/save (prepare-message (merge default-values + message + {:from (or from "anonymous") + :timestamp (random/timestamp)}))))) (defn update-message [{:keys [message-id] :as message}] - (when (data-store/exists? message-id) - (let [message (-> message - (utils/update-if-present :user-statuses vals) - (utils/update-if-present :content prepare-content))] - (data-store/save message)))) + (when-let [{:keys [chat-id]} (data-store/get-by-id message-id)] + (data-store/save (prepare-message (assoc message :chat-id chat-id))))) (defn delete-by-chat-id [chat-id] (data-store/delete-by-chat-id chat-id)) diff --git a/src/status_im/data_store/pending_messages.cljs b/src/status_im/data_store/pending_messages.cljs index 8f7dcf24c4..9adb15a004 100644 --- a/src/status_im/data_store/pending_messages.cljs +++ b/src/status_im/data_store/pending_messages.cljs @@ -41,8 +41,8 @@ (data-store/save message'))) (defn delete - [pending-message] - (data-store/delete pending-message)) + [message-id] + (data-store/delete message-id)) (defn delete-all-by-chat-id [chat-id] diff --git a/src/status_im/data_store/realm/messages.cljs b/src/status_im/data_store/realm/messages.cljs index eeaf74d316..6ec762a326 100644 --- a/src/status_im/data_store/realm/messages.cljs +++ b/src/status_im/data_store/realm/messages.cljs @@ -11,12 +11,18 @@ [] (realm/js-object->clj (get-all))) +(defn- transform-message [message] + (update message :user-statuses + (partial into {} + (map (fn [[_ {:keys [whisper-identity status]}]] + [whisper-identity (keyword status)]))))) + (defn get-by-id [message-id] - (when-let [message (realm/get-one-by-field-clj @realm/account-realm :message :message-id message-id)] - (realm/fix-map message :user-statuses :whisper-identity))) + (some-> (realm/get-one-by-field-clj @realm/account-realm :message :message-id message-id) + transform-message)) -(defn get-by-chat-id +(defn get-by-chat-id ([chat-id number-of-messages] (get-by-chat-id chat-id 0 number-of-messages)) ([chat-id from number-of-messages] @@ -24,7 +30,7 @@ (realm/sorted :timestamp :desc) (realm/page from (+ from number-of-messages)) realm/js-object->clj)] - (mapv #(realm/fix-map % :user-statuses :whisper-identity) messages)))) + (mapv transform-message messages)))) (defn get-by-fields [fields from number-of-messages] @@ -40,10 +46,10 @@ (realm/single-clj))) (defn get-unviewed - [] + [current-public-key] (-> @realm/account-realm - (realm/get-by-fields :message :and {:outgoing false - :message-status nil}) + (realm/get-by-fields :user-status :and {:whisper-identity current-public-key + :status :received}) realm/js-object->clj)) (defn exists? @@ -52,8 +58,7 @@ (defn save [message] - (let [message (update message :user-statuses #(if % % []))] - (realm/save @realm/account-realm :message message true))) + (realm/save @realm/account-realm :message message true)) (defn delete-by-chat-id [chat-id] diff --git a/src/status_im/data_store/realm/pending_messages.cljs b/src/status_im/data_store/realm/pending_messages.cljs index 124c974473..21d787b4a1 100644 --- a/src/status_im/data_store/realm/pending_messages.cljs +++ b/src/status_im/data_store/realm/pending_messages.cljs @@ -23,9 +23,8 @@ (realm/save @realm/account-realm :pending-message pending-message true)) (defn delete - [{{:keys [message-id ack-of-message]} :payload}] - (let [message-id (or ack-of-message message-id)] - (realm/delete @realm/account-realm (get-by-message-id message-id)))) + [message-id] + (realm/delete @realm/account-realm (get-by-message-id message-id))) (defn delete-all-by-chat-id [chat-id] diff --git a/src/status_im/data_store/realm/schemas/account/v19/core.cljs b/src/status_im/data_store/realm/schemas/account/v19/core.cljs index d4fbecd48a..51fb4d0991 100644 --- a/src/status_im/data_store/realm/schemas/account/v19/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/v19/core.cljs @@ -8,7 +8,7 @@ [status-im.data-store.realm.schemas.account.v1.processed-message :as processed-message] [status-im.data-store.realm.schemas.account.v19.request :as request] [status-im.data-store.realm.schemas.account.v1.tag :as tag] - [status-im.data-store.realm.schemas.account.v1.user-status :as user-status] + [status-im.data-store.realm.schemas.account.v19.user-status :as user-status] [status-im.data-store.realm.schemas.account.v5.contact-group :as contact-group] [status-im.data-store.realm.schemas.account.v5.group-contact :as group-contact] [status-im.data-store.realm.schemas.account.v8.local-storage :as local-storage] @@ -101,6 +101,24 @@ (log/debug "migrating v19 command/request database, updating: " content " with: " new-props) (aset object "content" (pr-str new-content))))))) +(defn update-message-statuses [new-realm] + (some-> new-realm + (.objects "message") + (.map (fn [msg _ _] + (let [message-id (aget msg "message-id") + chat-id (aget msg "chat-id") + from (aget msg "from") + msg-status (aget msg "message-status") + statuses (aget msg "user-statuses")] + (when statuses + (.map statuses (fn [status _ _] + (aset status "message-id" message-id) + (aset status "chat-id" chat-id))) + (.push statuses (clj->js {"message-id" message-id + "chat-id" chat-id + "status" (or msg-status "received") + "whisper-identity" (or from "anonymous")})))))))) + (defn migration [old-realm new-realm] (log/debug "migrating v19 account database: " old-realm new-realm) (remove-contact! new-realm "transactor-personal") @@ -108,4 +126,5 @@ (remove-console-intro-message! new-realm) (update-commands (juxt :bot :command) owner-command->new-props new-realm "command") (update-commands (juxt :command) console-requests->new-props new-realm "command-request") - (update-commands (juxt :command (comp count :prefill)) transactor-requests->new-props new-realm "command-request")) + (update-commands (juxt :command (comp count :prefill)) transactor-requests->new-props new-realm "command-request") + (update-message-statuses new-realm)) diff --git a/src/status_im/data_store/realm/schemas/account/v19/message.cljs b/src/status_im/data_store/realm/schemas/account/v19/message.cljs index 346ff1a5c7..a03d46a75c 100644 --- a/src/status_im/data_store/realm/schemas/account/v19/message.cljs +++ b/src/status_im/data_store/realm/schemas/account/v19/message.cljs @@ -17,13 +17,13 @@ :indexed true} :outgoing :bool :retry-count {:type :int - :default 0} + :default 0} :message-type {:type :string :optional true} :message-status {:type :string :optional true} :user-statuses {:type :list - :objectType "user-status"} + :objectType :user-status} :clock-value {:type :int :default 0} :show? {:type :bool diff --git a/src/status_im/data_store/realm/schemas/account/v19/user_status.cljs b/src/status_im/data_store/realm/schemas/account/v19/user_status.cljs new file mode 100644 index 0000000000..6d1f99ac66 --- /dev/null +++ b/src/status_im/data_store/realm/schemas/account/v19/user_status.cljs @@ -0,0 +1,7 @@ +(ns status-im.data-store.realm.schemas.account.v19.user-status) + +(def schema {:name :user-status + :properties {:message-id :string + :chat-id :string + :whisper-identity :string + :status :string}}) diff --git a/src/status_im/protocol/handlers.cljs b/src/status_im/protocol/handlers.cljs index bf8227ccf4..d0c31f8965 100644 --- a/src/status_im/protocol/handlers.cljs +++ b/src/status_im/protocol/handlers.cljs @@ -11,6 +11,7 @@ [status-im.i18n :as i18n] [status-im.utils.random :as random] [status-im.protocol.message-cache :as cache] + [status-im.chat.utils :as chat.utils] [status-im.utils.datetime :as datetime] [taoensso.timbre :as log :refer-macros [debug]] [status-im.native-module.core :as status] @@ -197,39 +198,15 @@ :content (str (or inviter-name from) " " (i18n/label :t/invited) " " (or invitee-name identity)) :content-type constants/text-content-type}])))) -(re-frame/reg-fx - ::save-message-status! - (fn [{:keys [message-id ack-of-message group-id from status]}] - (let [message-id' (or ack-of-message message-id)] - (when-let [{:keys [message-status] :as message} (messages/get-by-id message-id')] - (when-not (= (keyword message-status) :seen) - (let [group? (boolean group-id) - message' (-> (if (and group? (not= status :sent)) - (update-in message - [:user-statuses from] - (fn [{old-status :status}] - {:id (random/id) - :whisper-identity from - :status (if (= (keyword old-status) :seen) - old-status - status)})) - (assoc message :message-status status)) - ;; we need to dissoc preview because it has been saved before - (dissoc :preview))] - (messages/update-message message'))))))) - (re-frame/reg-fx ::pending-messages-delete - (fn [message] - (pending-messages/delete message))) + (fn [message-id] + (pending-messages/delete message-id))) (re-frame/reg-fx ::pending-messages-save - (fn [{:keys [type id pending-message]}] - (pending-messages/save pending-message) - (when (#{:message :group-message} type) - (messages/update-message {:message-id id - :delivery-status :pending})))) + (fn [pending-message] + (pending-messages/save pending-message))) (re-frame/reg-fx ::status-init-jail @@ -316,9 +293,18 @@ :to to :chat-id from})) +(defn- message-from-self [{:keys [current-public-key]} {:keys [id to group-id]}] + {:from to + :sent-from current-public-key + :payload {:message-id id + :group-id group-id}}) + +(defn- get-message-id [{:keys [message-id ack-of-message]}] + (or ack-of-message message-id)) + (handlers/register-handler-fx - :incoming-message - (fn [_ [_ type {:keys [payload ttl id] :as message}]] + :incoming-message + (fn [{:keys [db]} [_ type {:keys [payload ttl id] :as message}]] (let [message-id (or id (:message-id payload))] (when-not (cache/exists? message-id type) (let [ttl-s (* 1000 (or ttl 120)) @@ -326,74 +312,56 @@ :message-id message-id :type type :ttl (+ (datetime/now-ms) ttl-s)} - route-event (case type - (:message - :group-message - :public-group-message) [:chat-received-message/add (transform-protocol-message message)] - :ack (if (#{:message :group-message} (:type payload)) - [:update-message-status message :delivered] - [:pending-message-remove message]) - :seen [:update-message-status message :seen] - :group-invitation [:group-chat-invite-received message] - :update-group [:update-group-message message] - :add-group-identity [:participant-invited-to-group message] - :remove-group-identity [:participant-removed-from-group message] - :leave-group [:participant-left-group message] - :contact-request [:contact-request-received message] - :discover [:status-received message] - :discoveries-request [:discoveries-request-received message] - :discoveries-response [:discoveries-response-received message] - :profile [:contact-update-received message] - :update-keys [:update-keys-received message] - :online [:contact-online-received message] - :pending [:pending-message-upsert message] - :sent (let [{:keys [to id group-id]} message - message' {:from to - :payload {:message-id id - :group-id group-id}}] - [:update-message-status message' :sent]) - nil)] - (when (nil? route-event) (debug "Unknown message type" type)) + chat-message (#{:message :group-message} (:type payload)) + route-fx (case type + (:message + :group-message + :public-group-message) {:dispatch [:chat-received-message/add (transform-protocol-message message)]} + :pending (cond-> {::pending-messages-save message} + chat-message + (assoc :dispatch + [:update-message-status (message-from-self db message) :pending])) + :sent {:dispatch [:update-message-status (message-from-self db message) :sent]} + :ack (cond-> {::pending-messages-delete (get-message-id payload)} + chat-message + (assoc :dispatch [:update-message-status message :delivered])) + :seen {:dispatch [:update-message-status message :seen]} + :group-invitation {:dispatch [:group-chat-invite-received message]} + :update-group {:dispatch [:update-group-message message]} + :add-group-identity {:dispatch [:participant-invited-to-group message]} + :remove-group-identity {:dispatch [:participant-removed-from-group message]} + :leave-group {:dispatch [:participant-left-group message]} + :contact-request {:dispatch [:contact-request-received message]} + :discover {:dispatch [:status-received message]} + :discoveries-request {:dispatch [:discoveries-request-received message]} + :discoveries-response {:dispatch [:discoveries-response-received message]} + :profile {:dispatch [:contact-update-received message]} + :update-keys {:dispatch [:update-keys-received message]} + :online {:dispatch [:contact-online-received message]} + nil)] + (when (nil? route-fx) (debug "Unknown message type" type)) (cache/add! processed-message) (merge {::save-processed-messages processed-message} - (when route-event {:dispatch route-event}))))))) - -(defn update-message-status [db {:keys [message-id ack-of-message group-id from status]}] - (let [message-id' (or ack-of-message message-id) - update-group-status? (and group-id (not= status :sent)) - message-path [:chats (or group-id from) :messages message-id'] - current-status (if update-group-status? - (get-in db (into message-path [:user-statuses from :status])) - (get-in db (into message-path [:message-status])))] - ;; for some strange reason, we sometimes receive status update for message we don't have, - ;; that's why the first condition in if - (if (and (get-in db message-path) - (not= :seen current-status)) - (if update-group-status? - (assoc-in db (into message-path [:user-statuses from]) {:whisper-identity from - :status status}) - (assoc-in db (into message-path [:message-status]) status)) - db))) + route-fx)))))) (handlers/register-handler-fx :update-message-status [re-frame/trim-v + (re-frame/inject-cofx :get-stored-message) (re-frame/inject-cofx ::chats-is-active?)] - (fn [{db :db chats-is-active? :chats-is-active?} - [{:keys [from] - {:keys [message-id ack-of-message group-id]} :payload - :as message} - status]] - (let [data {:status status - :message-id message-id :ack-of-message ack-of-message - :group-id group-id :from from}] - (merge - {::save-message-status! data} - (when (= status :delivered) - {:dispatch [:pending-message-remove message]}) - (when chats-is-active? - {:db (update-message-status db data)}))))) + (fn [{:keys [db chats-is-active? get-stored-message]} [{:keys [from sent-from payload]} status]] + (let [message-identifier (get-message-id payload) + message-db-path [:chats (or (:group-id payload) from) :messages message-identifier] + from-id (or sent-from from) + message (get-stored-message message-identifier)] + ;; proceed with updating status if chat is active, and message was not already seen + (when (and chats-is-active? (not (chat.utils/message-seen-by? message from-id))) + (let [statuses (assoc (:user-statuses message) from-id status)] + (cond-> {:update-message {:message-id message-identifier + :user-statuses statuses}} + (get-in db message-db-path) + (assoc :db (assoc-in db (conj message-db-path :user-statuses) statuses)))))))) (handlers/register-handler-fx :contact-request-received @@ -423,23 +391,6 @@ [:update-chat! chat] [:watch-contact contact]]})))))) -(handlers/register-handler-fx - :pending-message-upsert - (fn [{db :db} [_ {:keys [type id to group-id] :as pending-message}]] - (let [chat-id (or group-id to) - current-status (get-in db [:message-status chat-id id])] - (merge - {::pending-messages-save {:type type :id id :pending-message pending-message}} - (when (and (#{:message :group-message} type) (not= :seen current-status)) - {:db (assoc-in db [:message-status chat-id id] :pending)}))))) - -(handlers/register-handler-fx - :pending-message-remove - (fn [_ [_ message]] - {::pending-messages-delete message})) - - - ;;GROUP (handlers/register-handler-fx diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index 0ff9022f3f..684ea68d00 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -21,7 +21,7 @@ :gas-price ethereum/default-gas-price}) ;; initial state of app-db -(def app-db {:current-public-key "" +(def app-db {:current-public-key nil :status-module-initialized? (or platform/ios? js/goog.DEBUG) :keyboard-height 0 :accounts/accounts {} @@ -162,8 +162,7 @@ :chat/message-data :chat/message-status :chat/selected-participants - :chat/chat-loaded-callbacks - :chat/command-hash-valid? + :chat/chat-loaded-callbacks :chat/public-group-topic :chat/confirmation-code-sms-listener :chat/messages