Refactored statuses

This commit is contained in:
janherich 2017-12-23 21:27:04 +01:00
parent e66b5c435b
commit 52ddccca96
No known key found for this signature in database
GPG Key ID: C23B473AFBE94D13
19 changed files with 287 additions and 320 deletions

View File

@ -4,7 +4,6 @@
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.utils.gfycat.core :as gfycat] [status-im.utils.gfycat.core :as gfycat]
[status-im.chat.models :as model] [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.console :as console-chat]
[status-im.chat.constants :as chat-const] [status-im.chat.constants :as chat-const]
[status-im.data-store.messages :as msg-store] [status-im.data-store.messages :as msg-store]
@ -31,7 +30,8 @@
(re-frame/reg-cofx (re-frame/reg-cofx
:stored-unviewed-messages :stored-unviewed-messages
(fn [cofx _] (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 (re-frame/reg-cofx
:get-stored-message :get-stored-message
@ -170,14 +170,14 @@
(if account-creation? (if account-creation?
{:db db {:db db
:dispatch load-default-contacts-event} :dispatch load-default-contacts-event}
(let [chat->unviewed-messages (unviewed-messages-model/index-unviewed-messages stored-unviewed-messages) (let [chat->message-id->request (reduce (fn [acc {:keys [chat-id message-id] :as request}]
chat->message-id->request (reduce (fn [acc {:keys [chat-id message-id] :as request}]
(assoc-in acc [chat-id message-id] request)) (assoc-in acc [chat-id message-id] request))
{} {}
stored-unanswered-requests) stored-unanswered-requests)
chats (reduce (fn [acc {:keys [chat-id] :as chat}] chats (reduce (fn [acc {:keys [chat-id] :as chat}]
(assoc acc chat-id (assoc chat (assoc acc chat-id
:unviewed-messages (get chat->unviewed-messages chat-id) (assoc chat
:unviewed-messages (get stored-unviewed-messages chat-id)
:requests (get chat->message-id->request chat-id) :requests (get chat->message-id->request chat-id)
:messages (index-messages (get-stored-messages chat-id))))) :messages (index-messages (get-stored-messages chat-id)))))
{} {}
@ -190,20 +190,19 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:send-seen! :send-seen!
[re-frame/trim-v] [re-frame/trim-v]
(fn [{:keys [db]} [{:keys [message-id chat-id from]}]] (fn [{:keys [db]} [{:keys [chat-id from me message-id]}]]
(let [{:keys [web3 current-public-key chats] (let [{:keys [web3 chats] :contacts/keys [contacts]} db
:contacts/keys [contacts]} db {:keys [group-chat public? messages]} (get chats chat-id)
{:keys [group-chat public?]} (get chats chat-id)] statuses (assoc (get-in messages [message-id :user-statuses]) me :seen)]
(cond-> {:db (-> db (cond-> {:db (-> db
(unviewed-messages-model/remove-unviewed-message chat-id message-id) (update-in [:chats chat-id :unviewed-messages] disj message-id)
(assoc-in [:chats chat-id :messages message-id :message-status] :seen)) (assoc-in [:chats chat-id :messages message-id :user-statuses] statuses))
:update-message {:message-id message-id :update-message {:message-id message-id
:message-status :seen}} :user-statuses statuses}}
(and (not (get-in contacts [chat-id] :dapp?)) ;; for public chats and 1-1 bot/dapp chats, it makes no sense to signalise `:seen` msg
(not public?)) (not (or public? (get-in contacts [chat-id] :dapp?)))
(assoc :protocol-send-seen (assoc :protocol-send-seen {:web3 web3
{:web3 web3 :message (cond-> {:from me
:message (cond-> {:from current-public-key
:to from :to from
:message-id message-id} :message-id message-id}
group-chat (assoc :group-id chat-id))}))))) group-chat (assoc :group-id chat-id))})))))

View File

@ -44,10 +44,13 @@
(sign-up db phone-number message-id))) (sign-up db phone-number message-id)))
(defn- message-seen [{:keys [db] :as fx} message-id] (defn- message-seen [{:keys [db] :as fx} message-id]
(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 (-> fx
(assoc-in [:db :chats const/console-chat-id :messages message-id :message-status] :seen) (assoc-in (into [:db] statuses-path) statuses)
(assoc :update-message {:message-id message-id (assoc :update-message {:message-id message-id
:message-status :seen}))) :user-statuses statuses}))))
(handlers/register-handler-fx (handlers/register-handler-fx
:start-listening-confirmation-code-sms :start-listening-confirmation-code-sms

View File

@ -79,9 +79,15 @@
(dispatch [:prepare-command! chat-id params]))) (dispatch [:prepare-command! chat-id params])))
(dispatch [:set-chat-ui-props {:sending-in-progress? false}])))) (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! (register-handler :prepare-command!
(u/side-effect! (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 [_ add-to-chat-id {{:keys [handler-data
command] command]
:as content} :command :as content} :command
@ -92,7 +98,8 @@
hidden-params (->> (:params command) hidden-params (->> (:params command)
(filter :hidden) (filter :hidden)
(map :name)) (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 [:update-message-overhead! chat-id network-status])
(dispatch [:set-chat-ui-props {:sending-in-progress? false}]) (dispatch [:set-chat-ui-props {:sending-in-progress? false}])
(dispatch [::send-command! add-to-chat-id (assoc params :command command') hidden-params]) (dispatch [::send-command! add-to-chat-id (assoc params :command command') hidden-params])

View File

@ -5,7 +5,6 @@
[status-im.chat.utils :as chat-utils] [status-im.chat.utils :as chat-utils]
[status-im.chat.models :as chat-model] [status-im.chat.models :as chat-model]
[status-im.chat.models.commands :as commands-model] [status-im.chat.models.commands :as commands-model]
[status-im.chat.models.unviewed-messages :as unviewed-messages-model]
[status-im.chat.events.requests :as requests-events] [status-im.chat.events.requests :as requests-events]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
@ -33,8 +32,8 @@
(defn- add-message-to-db (defn- add-message-to-db
[db {:keys [message-id] :as message} chat-id] [db {:keys [message-id] :as message} chat-id]
(-> db (-> db
(chat-utils/add-message-to-db chat-id chat-id message (:new? message)) (update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id)
(unviewed-messages-model/add-unviewed-message chat-id message-id))) (chat-utils/add-message-to-db chat-id chat-id message (:new? message))))
(defn receive (defn receive
[{:keys [db message-exists? pop-up-chat? get-last-clock-value now] :as cofx} [{:keys [db message-exists? pop-up-chat? get-last-clock-value now] :as cofx}
@ -63,6 +62,8 @@
:clock-value (clocks/receive :clock-value (clocks/receive
clock-value clock-value
(get-last-clock-value chat-identifier))) (get-last-clock-value chat-identifier)))
public-key
(assoc :user-statuses {public-key :received})
(and command command-request?) (and command command-request?)
(assoc-in [:content :content-command-ref] (assoc-in [:content :content-command-ref]
(lookup-response-ref access-scope->commands-responses (lookup-response-ref access-scope->commands-responses

View File

@ -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))

View File

@ -10,10 +10,8 @@
(s/def :chat/chat-list-ui-props (s/nilable map?)) (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/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/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/selected-participants (s/nilable set?))
(s/def :chat/chat-loaded-callbacks (s/nilable map?)) (s/def :chat/chat-loaded-callbacks (s/nilable map?))
(s/def :chat/command-hash-valid? (s/nilable boolean?))
(s/def :chat/public-group-topic (s/nilable string?)) (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/confirmation-code-sms-listener (s/nilable any?)) ; .addListener result object
(s/def :chat/messages (s/nilable map?)) ; messages indexed by message-id (s/def :chat/messages (s/nilable map?)) ; messages indexed by message-id

View File

@ -61,6 +61,12 @@
(fn [[chats current-chat-id]] (fn [[chats current-chat-id]]
(get 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 (reg-sub
:chat :chat
:<- [:chats] :<- [:chats]
@ -265,7 +271,7 @@
(:web-view-extra-js current-chat))) (:web-view-extra-js current-chat)))
(reg-sub (reg-sub
:photo-path :get-photo-path
:<- [:get-contacts] :<- [:get-contacts]
(fn [contacts [_ id]] (fn [contacts [_ id]]
(:photo-path (contacts id)))) (:photo-path (contacts id))))

View File

@ -9,5 +9,8 @@
:new? (if (nil? new?) true new?))] :new? (if (nil? new?) true new?))]
(update-in db [:chats add-to-chat-id :messages] assoc message-id prepared-message)))) (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]}] (defn command-name [{:keys [name]}]
(str chat.constants/command-char name)) (str chat.constants/command-char name))

View File

@ -1,72 +1,63 @@
(ns status-im.chat.views.input.suggestions (ns status-im.chat.views.input.suggestions
(:require-macros [status-im.utils.views :refer [defview]]) (:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch]] (:require [re-frame.core :as re-frame]
[status-im.ui.components.react :refer [view [status-im.ui.components.react :as react]
scroll-view
touchable-highlight
text
icon]]
[status-im.data-store.messages :as messages]
[status-im.chat.styles.input.suggestions :as style] [status-im.chat.styles.input.suggestions :as style]
[status-im.chat.constants :as const] [status-im.chat.views.input.animations.expandable :as expandable]
[status-im.chat.views.input.animations.expandable :refer [expandable-view]] [status-im.chat.utils :as chat.utils]
[status-im.chat.views.input.utils :as input-utils] [status-im.i18n :as i18n]))
[status-im.i18n :refer [label]]
[taoensso.timbre :as log]
[status-im.chat.utils :as chat-utils]))
(defn suggestion-item [{:keys [on-press name description last?]}] (defn suggestion-item [{:keys [on-press name description last?]}]
[touchable-highlight {:on-press on-press} [react/touchable-highlight {:on-press on-press}
[view (style/item-suggestion-container last?) [react/view (style/item-suggestion-container last?)
[view {:style style/item-suggestion-name} [react/view {:style style/item-suggestion-name}
[text {:style style/item-suggestion-name-text [react/text {:style style/item-suggestion-name-text
:font :roboto-mono} name]] :font :roboto-mono} name]]
[text {:style style/item-suggestion-description [react/text {:style style/item-suggestion-description
:number-of-lines 2} :number-of-lines 2}
description]]]) description]]])
(defview response-item [{:keys [name description] (defview response-item [{:keys [name description]
{:keys [type message-id]} :request :as command} last?] {:keys [message-id]} :request :as command} last?]
[{:keys [chat-id]} [:get-current-chat]] [{{:keys [params]} :content} [:get-current-chat-message message-id]]
[suggestion-item [suggestion-item
{:on-press #(let [{:keys [params]} (messages/get-message-content-by-id message-id) {:on-press #(let [metadata (assoc params :to-message-id message-id)]
metadata (assoc params :to-message-id message-id)] (re-frame/dispatch [:select-chat-input-command command metadata]))
(dispatch [:select-chat-input-command command metadata])) :name (chat.utils/command-name command)
:name (chat-utils/command-name command)
:description description :description description
:last? last?}]) :last? last?}])
(defn command-item [{:keys [name description bot] :as command} last?] (defn command-item [{:keys [name description bot] :as command} last?]
[suggestion-item [suggestion-item
{:on-press #(dispatch [:select-chat-input-command command nil]) {:on-press #(re-frame/dispatch [:select-chat-input-command command nil])
:name (chat-utils/command-name command) :name (chat.utils/command-name command)
:description description :description description
:last? last?}]) :last? last?}])
(defn item-title [top-padding? s] (defn item-title [top-padding? title]
[view (style/item-title-container top-padding?) [react/view (style/item-title-container top-padding?)
[text {:style style/item-title-text} [react/text {:style style/item-title-text}
s]]) title]])
(defview suggestions-view [] (defview suggestions-view []
[show-suggestions? [:show-suggestions?] [show-suggestions? [:show-suggestions?]
responses [:get-available-responses] responses [:get-available-responses]
commands [:get-available-commands]] commands [:get-available-commands]]
(when show-suggestions? (when show-suggestions?
[expandable-view {:key :suggestions [expandable/expandable-view {:key :suggestions
:draggable? false :draggable? false
:height 212} :height 212}
[view {:flex 1} [react/view {:flex 1}
[scroll-view {:keyboardShouldPersistTaps :always} [react/scroll-view {:keyboardShouldPersistTaps :always}
(when (seq responses) (when (seq responses)
[view [react/view
[item-title false (label :t/suggestions-requests)] [item-title false (i18n/label :t/suggestions-requests)]
(for [[i response] (map-indexed vector responses)] (for [[i response] (map-indexed vector responses)]
^{:key i} ^{:key i}
[response-item response (= i (dec (count responses)))])]) [response-item response (= i (dec (count responses)))])])
(when (seq commands) (when (seq commands)
[view [react/view
[item-title (seq responses) (label :t/suggestions-commands)] [item-title (seq responses) (i18n/label :t/suggestions-commands)]
(for [[i command] (map-indexed vector commands)] (for [[i command] (map-indexed vector commands)]
^{:key i} ^{:key i}
[command-item command (= i (dec (count commands)))])])]]])) [command-item command (= i (dec (count commands)))])])]]]))

View File

@ -174,71 +174,66 @@
[message-content-audio {:content content [message-content-audio {:content content
:content-type content-type}]]]) :content-type content-type}]]])
(defview group-message-delivery-status [{:keys [message-id group-id message-status user-statuses] :as msg}] (defn- text-status [status]
(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/view style/delivery-view
[react/text {:style style/delivery-text [react/text {:style style/delivery-text
:font :default} :font :default}
(i18n/message-status-label (i18n/message-status-label status)]])
(if seen-by-everyone?
:seen-by-everyone (defview group-message-delivery-status [{:keys [message-id group-id current-public-key user-statuses] :as msg}]
status))]] (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 [react/touchable-highlight
{:on-press (fn [] {:on-press #(re-frame/dispatch [:show-message-details {:message-status outgoing-status
(re-frame/dispatch [:show-message-details {:message-status status :user-statuses delivery-statuses
:user-statuses user-statuses :participants participants}])}
:participants participants}]))}
[react/view style/delivery-view [react/view style/delivery-view
(for [[_ {:keys [whisper-identity]}] (take 3 user-statuses)] (for [[whisper-identity] (take 3 delivery-statuses)]
^{:key whisper-identity} ^{:key whisper-identity}
[react/image {:source {:uri (or (get-in contacts [whisper-identity :photo-path]) [react/image {:source {:uri (or (get-in contacts [whisper-identity :photo-path])
(identicon/identicon whisper-identity))} (identicon/identicon whisper-identity))}
:style {:width 16 :style {:width 16
:height 16 :height 16
:borderRadius 8}}]) :borderRadius 8}}])
(if (> (count user-statuses) 3) (if (> delivery-statuses-count 3)
[react/text {:style style/delivery-text [react/text {:style style/delivery-text
:font :default} :font :default}
(str "+ " (- (count user-statuses) 3))])]])))) (str "+ " (- delivery-statuses-count 3))])]]))))
(defn message-delivery-status (defn message-delivery-status
[{:keys [message-id chat-id message-status user-statuses content]}] [{:keys [message-id chat-id current-public-key user-statuses content]}]
(let [delivery-status (get-in user-statuses [chat-id :status]) (let [outgoing-status (or (get user-statuses current-public-key) :sending)
status (cond (and (not (console/commands-with-delivery-status (:command content))) delivery-status (get user-statuses chat-id)
(= constants/console-chat-id chat-id)) status (cond (and (= constants/console-chat-id chat-id)
(not (console/commands-with-delivery-status (:command content))))
:seen :seen
:else :else
(or delivery-status message-status :sending))] (or delivery-status outgoing-status))]
[react/view style/delivery-view [text-status status]))
[react/text {:style style/delivery-text
:font :default} (defn- photo [from photo-path]
(i18n/message-status-label status)]])) [react/view
[react/image {:source {:uri (if (string/blank? photo-path)
(identicon/identicon from)
photo-path)}
:style style/photo}]])
(defview member-photo [from] (defview member-photo [from]
(letsubs [photo-path [:photo-path from]] (letsubs [photo-path [:get-photo-path from]]
[react/view (photo from photo-path)))
[react/image {:source {:uri (if (string/blank? photo-path)
(identicon/identicon from)
photo-path)}
:style style/photo}]]))
(defview my-photo [from] (defview my-photo [from]
(letsubs [account [:get-current-account]] (letsubs [{:keys [photo-path]} [:get-current-account]]
(let [{:keys [photo-path]} account] (photo from photo-path)))
[react/view
[react/image {:source {:uri (if (string/blank? photo-path)
(identicon/identicon from)
photo-path)}
:style style/photo}]])))
(defn message-body (defn message-body
[{:keys [last-outgoing? message-type same-author? from outgoing] :as message} content] [{:keys [last-outgoing? message-type same-author? from outgoing] :as message} content]
@ -291,18 +286,16 @@
children)])})) children)])}))
(into [react/view] children))) (into [react/view] children)))
(defn chat-message [{:keys [outgoing message-id chat-id message-status user-statuses (defn chat-message [{:keys [outgoing message-id chat-id from current-public-key] :as message}]
from current-public-key] :as message}]
(reagent/create-class (reagent/create-class
{:display-name "chat-message" {:display-name "chat-message"
:component-did-mount :component-did-mount
#(when (and message-id ;; send `:seen` signal when we have signed-in user, message not from us and we didn't sent it already
chat-id #(when (and current-public-key message-id chat-id (not outgoing)
(not outgoing) (not (chat.utils/message-seen-by? message current-public-key)))
(not= :seen message-status)
(not= :seen (keyword (get-in user-statuses [current-public-key :status]))))
(re-frame/dispatch [:send-seen! {:chat-id chat-id (re-frame/dispatch [:send-seen! {:chat-id chat-id
:from from :from from
:me current-public-key
:message-id message-id}])) :message-id message-id}]))
:reagent-render :reagent-render
(fn [{:keys [outgoing group-chat content-type content] :as message}] (fn [{:keys [outgoing group-chat content-type content] :as message}]
@ -321,4 +314,5 @@
[react/view [react/view
(let [incoming-group (and group-chat (not outgoing))] (let [incoming-group (and group-chat (not outgoing))]
[message-content message-body (merge message [message-content message-body (merge message
{:incoming-group incoming-group})])]]])})) {:current-public-key current-public-key
:incoming-group incoming-group})])]]])}))

View File

@ -14,8 +14,7 @@
(def default-values (def default-values
{:outgoing false {:outgoing false
:to nil :to nil})
:preview nil})
(defn exists? [message-id] (defn exists? [message-id]
(data-store/exists? message-id)) (data-store/exists? message-id))
@ -24,17 +23,11 @@
[message-id] [message-id]
(data-store/get-by-id 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 (defn get-by-chat-id
([chat-id] ([chat-id]
(get-by-chat-id chat-id 0)) (get-by-chat-id chat-id 0))
([chat-id from] ([chat-id from]
(->> (data-store/get-by-chat-id chat-id from constants/default-number-of-messages) (->> (data-store/get-by-chat-id chat-id from constants/default-number-of-messages)
reverse
(keep (fn [{:keys [content-type preview] :as message}] (keep (fn [{:keys [content-type preview] :as message}]
(if (command-type? content-type) (if (command-type? content-type)
(update message :content reader/read-string) (update message :content reader/read-string)
@ -46,13 +39,6 @@
(filter #(= (:content-type %) constants/content-type-log-message)) (filter #(= (:content-type %) constants/content-type-log-message))
(map #(select-keys % [:content :timestamp])))) (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 (defn get-last-clock-value
[chat-id] [chat-id]
(if-let [message (data-store/get-last-message chat-id)] (if-let [message (data-store/get-last-message chat-id)]
@ -60,8 +46,11 @@
0)) 0))
(defn get-unviewed (defn get-unviewed
[] [current-public-key]
(data-store/get-unviewed)) (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] (defn- prepare-content [content]
(if (string? content) (if (string? content)
@ -71,23 +60,32 @@
;; option for command params instead ;; option for command params instead
(update content :params dissoc :password :password-confirmation)))) (update content :params dissoc :password :password-confirmation))))
(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 (defn save
[{:keys [message-id content] :as message}] [{:keys [message-id content from] :as message}]
(when-not (data-store/exists? message-id) (when-not (data-store/exists? message-id)
(let [content' (prepare-content content) (data-store/save (prepare-message (merge default-values
message' (merge default-values
message message
{:content content' {:from (or from "anonymous")
:timestamp (random/timestamp)})] :timestamp (random/timestamp)})))))
(data-store/save message'))))
(defn update-message (defn update-message
[{:keys [message-id] :as message}] [{:keys [message-id] :as message}]
(when (data-store/exists? message-id) (when-let [{:keys [chat-id]} (data-store/get-by-id message-id)]
(let [message (-> message (data-store/save (prepare-message (assoc message :chat-id chat-id)))))
(utils/update-if-present :user-statuses vals)
(utils/update-if-present :content prepare-content))]
(data-store/save message))))
(defn delete-by-chat-id [chat-id] (defn delete-by-chat-id [chat-id]
(data-store/delete-by-chat-id chat-id)) (data-store/delete-by-chat-id chat-id))

View File

@ -41,8 +41,8 @@
(data-store/save message'))) (data-store/save message')))
(defn delete (defn delete
[pending-message] [message-id]
(data-store/delete pending-message)) (data-store/delete message-id))
(defn delete-all-by-chat-id (defn delete-all-by-chat-id
[chat-id] [chat-id]

View File

@ -11,10 +11,16 @@
[] []
(realm/js-object->clj (get-all))) (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 (defn get-by-id
[message-id] [message-id]
(when-let [message (realm/get-one-by-field-clj @realm/account-realm :message :message-id message-id)] (some-> (realm/get-one-by-field-clj @realm/account-realm :message :message-id message-id)
(realm/fix-map message :user-statuses :whisper-identity))) transform-message))
(defn get-by-chat-id (defn get-by-chat-id
([chat-id number-of-messages] ([chat-id number-of-messages]
@ -24,7 +30,7 @@
(realm/sorted :timestamp :desc) (realm/sorted :timestamp :desc)
(realm/page from (+ from number-of-messages)) (realm/page from (+ from number-of-messages))
realm/js-object->clj)] realm/js-object->clj)]
(mapv #(realm/fix-map % :user-statuses :whisper-identity) messages)))) (mapv transform-message messages))))
(defn get-by-fields (defn get-by-fields
[fields from number-of-messages] [fields from number-of-messages]
@ -40,10 +46,10 @@
(realm/single-clj))) (realm/single-clj)))
(defn get-unviewed (defn get-unviewed
[] [current-public-key]
(-> @realm/account-realm (-> @realm/account-realm
(realm/get-by-fields :message :and {:outgoing false (realm/get-by-fields :user-status :and {:whisper-identity current-public-key
:message-status nil}) :status :received})
realm/js-object->clj)) realm/js-object->clj))
(defn exists? (defn exists?
@ -52,8 +58,7 @@
(defn save (defn save
[message] [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 (defn delete-by-chat-id
[chat-id] [chat-id]

View File

@ -23,9 +23,8 @@
(realm/save @realm/account-realm :pending-message pending-message true)) (realm/save @realm/account-realm :pending-message pending-message true))
(defn delete (defn delete
[{{:keys [message-id ack-of-message]} :payload}] [message-id]
(let [message-id (or ack-of-message message-id)] (realm/delete @realm/account-realm (get-by-message-id message-id)))
(realm/delete @realm/account-realm (get-by-message-id message-id))))
(defn delete-all-by-chat-id (defn delete-all-by-chat-id
[chat-id] [chat-id]

View File

@ -8,7 +8,7 @@
[status-im.data-store.realm.schemas.account.v1.processed-message :as processed-message] [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.v19.request :as request]
[status-im.data-store.realm.schemas.account.v1.tag :as tag] [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.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.v5.group-contact :as group-contact]
[status-im.data-store.realm.schemas.account.v8.local-storage :as local-storage] [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) (log/debug "migrating v19 command/request database, updating: " content " with: " new-props)
(aset object "content" (pr-str new-content))))))) (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] (defn migration [old-realm new-realm]
(log/debug "migrating v19 account database: " old-realm new-realm) (log/debug "migrating v19 account database: " old-realm new-realm)
(remove-contact! new-realm "transactor-personal") (remove-contact! new-realm "transactor-personal")
@ -108,4 +126,5 @@
(remove-console-intro-message! new-realm) (remove-console-intro-message! new-realm)
(update-commands (juxt :bot :command) owner-command->new-props new-realm "command") (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) 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))

View File

@ -23,7 +23,7 @@
:message-status {:type :string :message-status {:type :string
:optional true} :optional true}
:user-statuses {:type :list :user-statuses {:type :list
:objectType "user-status"} :objectType :user-status}
:clock-value {:type :int :clock-value {:type :int
:default 0} :default 0}
:show? {:type :bool :show? {:type :bool

View File

@ -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}})

View File

@ -11,6 +11,7 @@
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.utils.random :as random] [status-im.utils.random :as random]
[status-im.protocol.message-cache :as cache] [status-im.protocol.message-cache :as cache]
[status-im.chat.utils :as chat.utils]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
[taoensso.timbre :as log :refer-macros [debug]] [taoensso.timbre :as log :refer-macros [debug]]
[status-im.native-module.core :as status] [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 (str (or inviter-name from) " " (i18n/label :t/invited) " " (or invitee-name identity))
:content-type constants/text-content-type}])))) :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 (re-frame/reg-fx
::pending-messages-delete ::pending-messages-delete
(fn [message] (fn [message-id]
(pending-messages/delete message))) (pending-messages/delete message-id)))
(re-frame/reg-fx (re-frame/reg-fx
::pending-messages-save ::pending-messages-save
(fn [{:keys [type id pending-message]}] (fn [pending-message]
(pending-messages/save pending-message) (pending-messages/save pending-message)))
(when (#{:message :group-message} type)
(messages/update-message {:message-id id
:delivery-status :pending}))))
(re-frame/reg-fx (re-frame/reg-fx
::status-init-jail ::status-init-jail
@ -316,9 +293,18 @@
:to to :to to
:chat-id from})) :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 (handlers/register-handler-fx
:incoming-message :incoming-message
(fn [_ [_ type {:keys [payload ttl id] :as message}]] (fn [{:keys [db]} [_ type {:keys [payload ttl id] :as message}]]
(let [message-id (or id (:message-id payload))] (let [message-id (or id (:message-id payload))]
(when-not (cache/exists? message-id type) (when-not (cache/exists? message-id type)
(let [ttl-s (* 1000 (or ttl 120)) (let [ttl-s (* 1000 (or ttl 120))
@ -326,74 +312,56 @@
:message-id message-id :message-id message-id
:type type :type type
:ttl (+ (datetime/now-ms) ttl-s)} :ttl (+ (datetime/now-ms) ttl-s)}
route-event (case type chat-message (#{:message :group-message} (:type payload))
route-fx (case type
(:message (:message
:group-message :group-message
:public-group-message) [:chat-received-message/add (transform-protocol-message message)] :public-group-message) {:dispatch [:chat-received-message/add (transform-protocol-message message)]}
:ack (if (#{:message :group-message} (:type payload)) :pending (cond-> {::pending-messages-save message}
[:update-message-status message :delivered] chat-message
[:pending-message-remove message]) (assoc :dispatch
:seen [:update-message-status message :seen] [:update-message-status (message-from-self db message) :pending]))
:group-invitation [:group-chat-invite-received message] :sent {:dispatch [:update-message-status (message-from-self db message) :sent]}
:update-group [:update-group-message message] :ack (cond-> {::pending-messages-delete (get-message-id payload)}
:add-group-identity [:participant-invited-to-group message] chat-message
:remove-group-identity [:participant-removed-from-group message] (assoc :dispatch [:update-message-status message :delivered]))
:leave-group [:participant-left-group message] :seen {:dispatch [:update-message-status message :seen]}
:contact-request [:contact-request-received message] :group-invitation {:dispatch [:group-chat-invite-received message]}
:discover [:status-received message] :update-group {:dispatch [:update-group-message message]}
:discoveries-request [:discoveries-request-received message] :add-group-identity {:dispatch [:participant-invited-to-group message]}
:discoveries-response [:discoveries-response-received message] :remove-group-identity {:dispatch [:participant-removed-from-group message]}
:profile [:contact-update-received message] :leave-group {:dispatch [:participant-left-group message]}
:update-keys [:update-keys-received message] :contact-request {:dispatch [:contact-request-received message]}
:online [:contact-online-received message] :discover {:dispatch [:status-received message]}
:pending [:pending-message-upsert message] :discoveries-request {:dispatch [:discoveries-request-received message]}
:sent (let [{:keys [to id group-id]} message :discoveries-response {:dispatch [:discoveries-response-received message]}
message' {:from to :profile {:dispatch [:contact-update-received message]}
:payload {:message-id id :update-keys {:dispatch [:update-keys-received message]}
:group-id group-id}}] :online {:dispatch [:contact-online-received message]}
[:update-message-status message' :sent])
nil)] nil)]
(when (nil? route-event) (debug "Unknown message type" type)) (when (nil? route-fx) (debug "Unknown message type" type))
(cache/add! processed-message) (cache/add! processed-message)
(merge (merge
{::save-processed-messages processed-message} {::save-processed-messages processed-message}
(when route-event {:dispatch route-event}))))))) route-fx))))))
(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)))
(handlers/register-handler-fx (handlers/register-handler-fx
:update-message-status :update-message-status
[re-frame/trim-v [re-frame/trim-v
(re-frame/inject-cofx :get-stored-message)
(re-frame/inject-cofx ::chats-is-active?)] (re-frame/inject-cofx ::chats-is-active?)]
(fn [{db :db chats-is-active? :chats-is-active?} (fn [{:keys [db chats-is-active? get-stored-message]} [{:keys [from sent-from payload]} status]]
[{:keys [from] (let [message-identifier (get-message-id payload)
{:keys [message-id ack-of-message group-id]} :payload message-db-path [:chats (or (:group-id payload) from) :messages message-identifier]
:as message} from-id (or sent-from from)
status]] message (get-stored-message message-identifier)]
(let [data {:status status ;; proceed with updating status if chat is active, and message was not already seen
:message-id message-id :ack-of-message ack-of-message (when (and chats-is-active? (not (chat.utils/message-seen-by? message from-id)))
:group-id group-id :from from}] (let [statuses (assoc (:user-statuses message) from-id status)]
(merge (cond-> {:update-message {:message-id message-identifier
{::save-message-status! data} :user-statuses statuses}}
(when (= status :delivered) (get-in db message-db-path)
{:dispatch [:pending-message-remove message]}) (assoc :db (assoc-in db (conj message-db-path :user-statuses) statuses))))))))
(when chats-is-active?
{:db (update-message-status db data)})))))
(handlers/register-handler-fx (handlers/register-handler-fx
:contact-request-received :contact-request-received
@ -423,23 +391,6 @@
[:update-chat! chat] [:update-chat! chat]
[:watch-contact contact]]})))))) [: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 ;;GROUP
(handlers/register-handler-fx (handlers/register-handler-fx

View File

@ -21,7 +21,7 @@
:gas-price ethereum/default-gas-price}) :gas-price ethereum/default-gas-price})
;; initial state of app-db ;; 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) :status-module-initialized? (or platform/ios? js/goog.DEBUG)
:keyboard-height 0 :keyboard-height 0
:accounts/accounts {} :accounts/accounts {}
@ -163,7 +163,6 @@
:chat/message-status :chat/message-status
:chat/selected-participants :chat/selected-participants
:chat/chat-loaded-callbacks :chat/chat-loaded-callbacks
:chat/command-hash-valid?
:chat/public-group-topic :chat/public-group-topic
:chat/confirmation-code-sms-listener :chat/confirmation-code-sms-listener
:chat/messages :chat/messages