From c489c726351b1180d72f56ecdae77c74037e18e7 Mon Sep 17 00:00:00 2001 From: alwxndr Date: Fri, 16 Sep 2016 14:36:31 +0300 Subject: [PATCH] group chat statuses (#205) Former-commit-id: ea9ff21174bc31e93fd03bac490168751f8d1526 --- project.clj | 5 +- src/status_im/chat/handlers.cljs | 28 +++-- .../chat/handlers/receive_message.cljs | 4 +- src/status_im/chat/handlers/requests.cljs | 6 +- src/status_im/chat/handlers/send_message.cljs | 8 -- .../chat/handlers/unviewed_messages.cljs | 9 +- src/status_im/chat/screen.cljs | 22 ++-- src/status_im/chat/sign_up.cljs | 1 - src/status_im/chat/styles/screen.cljs | 64 +++++++++-- src/status_im/chat/subs.cljs | 11 +- src/status_im/chat/views/actions.cljs | 4 +- src/status_im/chat/views/bottom_info.cljs | 94 ++++++++++++++++ src/status_im/chat/views/message.cljs | 100 +++++++++++++----- src/status_im/chats_list/screen.cljs | 1 - src/status_im/components/status_bar.cljs | 1 - src/status_im/contacts/subs.cljs | 2 +- src/status_im/contacts/validations.cljs | 2 +- src/status_im/db.cljs | 3 +- src/status_im/discovery/model.cljs | 4 +- src/status_im/models/accounts.cljs | 2 +- src/status_im/models/chats.cljs | 58 +++++----- src/status_im/models/contacts.cljs | 2 +- src/status_im/models/messages.cljs | 38 ++++--- src/status_im/models/pending_messages.cljs | 2 +- src/status_im/models/requests.cljs | 2 +- src/status_im/persistence/realm/core.cljs | 6 +- src/status_im/persistence/realm/schemas.cljs | 52 +++++---- .../persistence/simple_kv_store.cljs | 2 +- src/status_im/protocol/handlers.cljs | 62 ++++++----- src/status_im/protocol/protocol_handler.cljs | 20 ++-- 30 files changed, 425 insertions(+), 190 deletions(-) create mode 100644 src/status_im/chat/views/bottom_info.cljs diff --git a/project.clj b/project.clj index 282e5b8ebd..69b35d795c 100644 --- a/project.clj +++ b/project.clj @@ -8,9 +8,8 @@ [reagent "0.5.1" :exclusions [cljsjs/react]] [re-frame "0.7.0"] [prismatic/schema "1.0.4"] - ^{:voom {:repo "git@github.com:status-im/status-lib.git" - :branch "master"}} - [status-im/protocol "0.2.2-20160909_082306-gcfbb92b"] + ^{:voom {:repo "git@github.com:status-im/status-lib.git" :branch "group-chat-statuses"}} + [status-im/protocol "0.2.3-20160914_155558-gfed628a"] [natal-shell "0.3.0"] [com.andrewmcveigh/cljs-time "0.4.0"] [tailrecursion/cljs-priority-map "1.2.0"] diff --git a/src/status_im/chat/handlers.cljs b/src/status_im/chat/handlers.cljs index fe869c6be3..325b47d799 100644 --- a/src/status_im/chat/handlers.cljs +++ b/src/status_im/chat/handlers.cljs @@ -36,9 +36,19 @@ status-im.chat.handlers.wallet-chat [status-im.utils.logging :as log])) -(register-handler :set-show-actions - (fn [db [_ show-actions]] - (assoc db :show-actions show-actions))) +(register-handler :set-chat-ui-props + (fn [db [_ ui-element value]] + (assoc-in db [:chat-ui-props ui-element] value))) + +(register-handler :set-show-info + (fn [db [_ show-info]] + (assoc db :show-info show-info))) + +(register-handler :show-message-details + (u/side-effect! + (fn [_ [_ details]] + (dispatch [:set-chat-ui-props :show-bottom-info? true]) + (dispatch [:set-chat-ui-props :bottom-info details])))) (register-handler :load-more-messages (fn [{:keys [current-chat-id loading-allowed] :as db} _] @@ -296,7 +306,6 @@ [{:keys [new-chat]} _] (chats/create-chat new-chat)) - (defn open-chat! [_ [_ chat-id _ navigation-type]] (dispatch [(or navigation-type :navigate-to) :chat chat-id])) @@ -395,11 +404,10 @@ (fn [db [_ h]] (assoc db :layout-height h))) - (register-handler :send-seen! (after (fn [_ [_ chat-id message-id]] - (when-not (console? chat-id)) - (dispatch [:message-seen chat-id message-id]))) + (dispatch [:message-seen {:message-id message-id + :chat-id chat-id}]))) (u/side-effect! (fn [_ [_ chat-id message-id]] (when-not (console? chat-id) @@ -419,16 +427,16 @@ (fn [db [_ chat-id mode]] (assoc-in db [:kb-mode chat-id] mode))) -(defn save-chat! +(defn update-chat! [_ [_ chat]] - (chats/create-chat chat)) + (chats/update-chat chat)) (register-handler :update-chat! (-> (fn [db [_ {:keys [chat-id] :as chat}]] (if (get-in db [:chats chat-id]) (update-in db [:chats chat-id] merge chat) db)) - ((after save-chat!)))) + ((after update-chat!)))) (register-handler :check-autorun (u/side-effect! diff --git a/src/status_im/chat/handlers/receive_message.cljs b/src/status_im/chat/handlers/receive_message.cljs index 71630b6eca..e838fa6b33 100644 --- a/src/status_im/chat/handlers/receive_message.cljs +++ b/src/status_im/chat/handlers/receive_message.cljs @@ -6,7 +6,8 @@ [status-im.commands.utils :refer [generate-hiccup]] [status-im.constants :refer [content-type-command-request]] [cljs.reader :refer [read-string]] - [status-im.models.chats :as c])) + [status-im.models.chats :as c] + [status-im.utils.logging :as log])) (defn check-preview [{:keys [content] :as message}] (if-let [preview (:preview content)] @@ -34,7 +35,6 @@ message' (assoc (->> message (cu/check-author-direction previous-message) (check-preview)) - :delivery-status :sending :chat-id chat-id')] (store-message message') (when-not (c/chat-exists? chat-id') diff --git a/src/status_im/chat/handlers/requests.cljs b/src/status_im/chat/handlers/requests.cljs index 97e80073c7..a4bd6a4788 100644 --- a/src/status_im/chat/handlers/requests.cljs +++ b/src/status_im/chat/handlers/requests.cljs @@ -24,10 +24,10 @@ [{:keys [current-chat-id] :as db} [_ chat-id]] (let [chat-id' (or chat-id current-chat-id) requests (-> ;; todo maybe limit is needed - (realm/get-by-fields :account :request :and [[:chat-id chat-id'] + (realm/get-by-fields :account :request :and [[:chat-id chat-id'] [:status "open"]]) - (realm/sorted :added :desc) - (realm/collection->map)) + (realm/sorted :added :desc) + (realm/realm-collection->list)) requests' (map #(update % :type keyword) requests)] (assoc-in db [:chats chat-id' :requests] requests'))) diff --git a/src/status_im/chat/handlers/send_message.cljs b/src/status_im/chat/handlers/send_message.cljs index 02e29f9d06..55b3867967 100644 --- a/src/status_im/chat/handlers/send_message.cljs +++ b/src/status_im/chat/handlers/send_message.cljs @@ -16,11 +16,6 @@ [status-im.protocol.api :as api] [status-im.utils.logging :as log])) -(defn default-delivery-status [chat-id] - (if (cu/console? chat-id) - :seen - :sending)) - (defn prepare-message [{:keys [identity current-chat-id] :as db} _] (let [text (get-in db [:chats current-chat-id :input-text]) @@ -33,7 +28,6 @@ :to current-chat-id :from identity :content-type text-content-type - :delivery-status (default-delivery-status current-chat-id) :outgoing true :timestamp (time/now-ms)})] (if command @@ -49,7 +43,6 @@ :to chat-id :content (assoc content :preview preview-string) :content-type content-type-command - :delivery-status (default-delivery-status chat-id) :outgoing true :preview preview-string :rendered-preview preview @@ -162,7 +155,6 @@ :content message :from identity :content-type text-content-type - :delivery-status (default-delivery-status chat-id) :outgoing true :timestamp (time/now-ms)}) message'' (if group-chat diff --git a/src/status_im/chat/handlers/unviewed_messages.cljs b/src/status_im/chat/handlers/unviewed_messages.cljs index 898f6a7fe8..c24ae9502f 100644 --- a/src/status_im/chat/handlers/unviewed_messages.cljs +++ b/src/status_im/chat/handlers/unviewed_messages.cljs @@ -1,12 +1,13 @@ (ns status-im.chat.handlers.unviewed-messages (:require [re-frame.core :refer [after enrich path dispatch]] [status-im.utils.handlers :refer [register-handler]] - [status-im.persistence.realm.core :as realm])) + [status-im.persistence.realm.core :as realm] + [status-im.utils.logging :as log])) (defn delivered-messages [] - (-> (realm/get-by-fields :account :message :and [[:delivery-status :delivered] - [:outgoing false]]) - (realm/collection->map))) + (-> (realm/get-by-fields :account :message :and {:outgoing false + :message-status nil}) + (realm/realm-collection->list))) (defn set-unviewed-messages [db] (let [messages (->> (::raw-unviewed-messages db) diff --git a/src/status_im/chat/screen.cljs b/src/status_im/chat/screen.cljs index b18cb6f353..101a084eee 100644 --- a/src/status_im/chat/screen.cljs +++ b/src/status_im/chat/screen.cljs @@ -24,6 +24,7 @@ [status-im.chat.views.response :refer [response-view]] [status-im.chat.views.new-message :refer [chat-message-new]] [status-im.chat.views.actions :refer [actions-view]] + [status-im.chat.views.bottom-info :refer [bottom-info-view]] [status-im.i18n :refer [label label-pluralize]] [status-im.components.animation :as anim] [reagent.core :as r] @@ -101,7 +102,7 @@ (defn toolbar-content [] (let [{:keys [group-chat name contacts chat-id]} (subscribe [:chat-properties [:group-chat :name :contacts :chat-id]]) - show-actions (subscribe [:show-actions]) + show-actions (subscribe [:chat-ui-props :show-actions?]) contact (subscribe [:get-in [:contacts @chat-id]])] (fn [] [view (st/chat-name-view @show-actions) @@ -123,20 +124,20 @@ (online-text @contact @chat-id)])]))) (defn toolbar-action [] - (let [show-actions (subscribe [:show-actions])] + (let [show-actions (subscribe [:chat-ui-props :show-actions?])] (fn [] (if @show-actions [touchable-highlight - {:on-press #(dispatch [:set-show-actions false])} + {:on-press #(dispatch [:set-chat-ui-props :show-actions? false])} [view st/action [icon :up st/up-icon]]] [touchable-highlight - {:on-press #(dispatch [:set-show-actions true])} + {:on-press #(dispatch [:set-chat-ui-props :show-actions? true])} [view st/action [chat-icon]]])))) (defview chat-toolbar [] - [show-actions [:show-actions]] + [show-actions [:chat-ui-props :show-actions?]] [view [status-bar] [toolbar {:hide-nav? show-actions @@ -182,7 +183,8 @@ (defn chat [] (let [group-chat (subscribe [:chat :group-chat]) - show-actions (subscribe [:show-actions]) + show-actions? (subscribe [:chat-ui-props :show-actions?]) + show-bottom-info? (subscribe [:chat-ui-props :show-bottom-info?]) command? (subscribe [:command?]) layout-height (subscribe [:get :layout-height])] (r/create-class @@ -200,6 +202,10 @@ ;; todo uncomment this #_(when @group-chat [typing-all]) [response-view] - (when-not @command? [suggestion-container]) + (when-not @command? + [suggestion-container]) [chat-message-new] - (when @show-actions [actions-view])])}))) + (when @show-actions? + [actions-view]) + (when @show-bottom-info? + [bottom-info-view])])}))) diff --git a/src/status_im/chat/sign_up.cljs b/src/status_im/chat/sign_up.cljs index 885818adf7..6c4b519247 100644 --- a/src/status_im/chat/sign_up.cljs +++ b/src/status_im/chat/sign_up.cljs @@ -157,7 +157,6 @@ (def intro-status {:message-id "intro-status" :content (label :t/intro-status) - :delivery-status "seen" :from "console" :chat-id "console" :content-type content-type-status diff --git a/src/status_im/chat/styles/screen.cljs b/src/status_im/chat/styles/screen.cljs index d71bbf2a83..8e3d9ff888 100644 --- a/src/status_im/chat/styles/screen.cljs +++ b/src/status_im/chat/styles/screen.cljs @@ -2,13 +2,15 @@ (:require [status-im.components.styles :refer [font title-font color-white + color-black chat-background online-color selected-message-color separator-color text1-color text2-color - toolbar-background1]])) + toolbar-background1]] + [status-im.utils.logging :as log])) (def chat-view {:flex 1 @@ -114,6 +116,13 @@ :color text2-color :font-size 12}) +(def actions-overlay + {:position :absolute + :top 0 + :bottom 0 + :left 0 + :right 0}) + (def typing-all {:marginBottom 20}) @@ -137,12 +146,51 @@ :fontFamily font :color text2-color}) -(def actions-overlay - {:position :absolute - :top 0 - :bottom 0 - :left 0 - :right 0}) - (def overlay-highlight {:flex 1}) + +;; this map looks a bit strange +;; but this way of setting elevation seems to be the only way to set z-index (in RN 0.30) +(def bottom-info-overlay + {:position :absolute + :top -16 + :bottom -16 + :left -16 + :right -16 + :background-color "#00000055" + :elevation 8}) + +(defn bottom-info-container [height] + {:backgroundColor toolbar-background1 + :elevation 2 + :position :absolute + :bottom 16 + :left 16 + :right 16 + :height height}) + +(def bottom-info-list-container + {:padding-left 16 + :padding-right 16 + :padding-top 8 + :padding-bottom 8}) + +(def bottom-info-row + {:flex-direction "row" + :padding-top 4 + :padding-bottom 4}) + +(def bottom-info-row-photo + {:width 42 + :height 42 + :borderRadius 21}) + +(def bottom-info-row-text-container + {:margin-left 16 + :margin-right 16}) + +(def bottom-info-row-text1 + {:color "black"}) + +(def bottom-info-row-text2 + {:color "#888888"}) \ No newline at end of file diff --git a/src/status_im/chat/subs.cljs b/src/status_im/chat/subs.cljs index bb2e24691a..77867865b3 100644 --- a/src/status_im/chat/subs.cljs +++ b/src/status_im/chat/subs.cljs @@ -3,6 +3,7 @@ (:require [re-frame.core :refer [register-sub dispatch subscribe path]] [status-im.utils.platform :refer [ios?]] [status-im.models.commands :as commands] + [status-im.models.chats :as chats] [status-im.constants :refer [response-suggesstion-resize-duration]] [status-im.chat.constants :as c] [status-im.handlers.content-suggestions :refer [get-content-suggestions]] @@ -19,9 +20,9 @@ (into {})))) (register-sub - :show-actions - (fn [db _] - (reaction (:show-actions @db)))) + :chat-ui-props + (fn [db [_ ui-element]] + (reaction (get-in @db [:chat-ui-props ui-element])))) (register-sub :chat (fn [db [_ k]] @@ -48,6 +49,10 @@ (fn [db _] (reaction (commands/get-commands @db)))) +(register-sub :get-chat-by-id + (fn [_ [_ chat-id]] + (reaction (chats/chat-by-id chat-id)))) + (register-sub :get-responses (fn [db _] (let [current-chat (@db :current-chat-id)] diff --git a/src/status_im/chat/views/actions.cljs b/src/status_im/chat/views/actions.cljs index ceaeff2757..d9539fd0d3 100644 --- a/src/status_im/chat/views/actions.cljs +++ b/src/status_im/chat/views/actions.cljs @@ -105,7 +105,7 @@ subtitle] icon-name :icon}] [touchable-highlight {:on-press (fn [] - (dispatch [:set-show-actions false]) + (dispatch [:set-chat-ui-props :show-actions? false]) (when handler (handler)))} [view st/action-icon-row @@ -138,5 +138,5 @@ ^{:key action} [action-view action]))]]))) (defn actions-view [] - [overlay {:on-click-outside #(dispatch [:set-show-actions false])} + [overlay {:on-click-outside #(dispatch [:set-chat-ui-props :show-actions? false])} [actions-list-view]]) \ No newline at end of file diff --git a/src/status_im/chat/views/bottom_info.cljs b/src/status_im/chat/views/bottom_info.cljs new file mode 100644 index 0000000000..9e1748b226 --- /dev/null +++ b/src/status_im/chat/views/bottom_info.cljs @@ -0,0 +1,94 @@ +(ns status-im.chat.views.bottom-info + (:require-macros [status-im.utils.views :refer [defview]]) + (:require [re-frame.core :refer [subscribe dispatch]] + [reagent.core :as r] + [status-im.components.react :refer [view + animated-view + image + text + icon + touchable-highlight + list-view + list-item]] + [status-im.components.chat-icon.screen :refer [chat-icon-view-menu-item]] + [status-im.chat.styles.screen :as st] + [status-im.i18n :refer [label label-pluralize message-status-label]] + [status-im.components.animation :as anim] + [status-im.utils.utils :refer [truncate-str]] + [status-im.utils.identicon :refer [identicon]] + [status-im.utils.listview :as lw] + [status-im.utils.logging :as log] + [clojure.string :as str])) + +(defn- container-animation-logic [{:keys [to-value val]}] + (fn [_] + (let [to-value @to-value] + (anim/start + (anim/spring val {:toValue to-value + :friction 6 + :tension 40}))))) + +(defn overlay [{:keys [on-click-outside]} items] + [view {:style st/bottom-info-overlay} + [touchable-highlight {:on-press on-click-outside + :style st/overlay-highlight} + [view nil]] + items]) + +(defn container [& _] + (let [layout-height (r/atom 0) + anim-value (anim/create-value 1) + context {:to-value layout-height + :val anim-value} + on-update (container-animation-logic context)] + (r/create-class + {:component-did-update + on-update + :reagent-render + (fn [& children] + @layout-height + [animated-view {:style (st/bottom-info-container anim-value)} + (into [view {:onLayout (fn [event] + (let [height (.. event -nativeEvent -layout -height)] + (reset! layout-height height)))}] + children)])}))) + +(defn message-status-row [{:keys [photo-path name]} {:keys [whisper-identity status]}] + [view st/bottom-info-row + [image {:source {:uri (or photo-path (identicon whisper-identity))} + :style st/bottom-info-row-photo}] + [view st/bottom-info-row-text-container + [text {:style st/bottom-info-row-text1 + :number-of-lines 1} + (truncate-str (if-not (str/blank? name) + name + whisper-identity) 30)] + [text {:style st/bottom-info-row-text2 + :number-of-lines 1} + (message-status-label (or status :sending))]]]) + +(defn render-row [contacts] + (fn [{:keys [whisper-identity] :as row} _ _] + (let [contact (get contacts whisper-identity)] + (list-item [message-status-row contact row])))) + +(defn bottom-info-view [] + (let [bottom-info (subscribe [:chat-ui-props :bottom-info]) + contacts (subscribe [:get-contacts])] + (r/create-class + {:reagent-render + (fn [] + (let [{:keys [user-statuses message-status participants]} @bottom-info + participants (->> participants + (map (fn [{:keys [identity]}] + [identity {:whisper-identity identity + :status message-status}])) + (into {}))] + [overlay {:on-click-outside #(dispatch [:set-chat-ui-props :show-bottom-info? false])} + [container + [list-view {:dataSource (-> (merge participants user-statuses) + (vals) + (lw/to-datasource)) + :enableEmptySections true + :renderRow (render-row @contacts) + :contentContainerStyle st/bottom-info-list-container}]]]))}))) \ No newline at end of file diff --git a/src/status_im/chat/views/message.cljs b/src/status_im/chat/views/message.cljs index b15de0b5ca..0a12e1acd5 100644 --- a/src/status_im/chat/views/message.cljs +++ b/src/status_im/chat/views/message.cljs @@ -12,6 +12,7 @@ [status-im.components.animation :as anim] [status-im.chat.views.request-message :refer [message-content-command-request]] [status-im.chat.styles.message :as st] + [status-im.models.chats :refer [chat-by-id]] [status-im.models.commands :refer [parse-command-message-content parse-command-request]] [status-im.resources :as res] @@ -20,7 +21,10 @@ content-type-status content-type-command content-type-command-request]] - [status-im.utils.logging :as log])) + [status-im.utils.logging :as log] + [status-im.protocol.api :as api] + [status-im.utils.identicon :refer [identicon]] + [status-im.chat.utils :as cu])) (defn message-date [timestamp] [view {} @@ -131,18 +135,64 @@ [message-content-audio {:content content :content-type content-type}]]]) -(defview message-delivery-status [{:keys [delivery-status chat-id message-id] :as message}] - [status [:get-in [:message-status chat-id message-id]]] - [view st/delivery-view - [image {:source (case (or status delivery-status) - :seen {:uri :icon_ok_small} - :seen-by-everyone {:uri :icon_ok_small} - :failed res/delivery-failed-icon - nil) - :style st/delivery-image}] - [text {:style st/delivery-text - :font :default} - (message-status-label (or status delivery-status))]]) +(defview group-message-delivery-status [{:keys [message-id group-id message-status user-statuses] :as msg}] + [app-db-message-user-statuses [:get-in [:message-user-statuses message-id]] + app-db-message-status-value [:get-in [:message-statuses message-id :status]] + chat [:get-chat-by-id group-id] + contacts [:get-contacts]] + (let [status (or message-status app-db-message-status-value :sending) + user-statuses (merge user-statuses app-db-message-user-statuses) + 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?) + [view st/delivery-view + [image {:source (case status + :seen {:uri :icon_ok_small} + :failed res/delivery-failed-icon + nil) + :style st/delivery-image}] + [text {:style st/delivery-text + :font :default} + (message-status-label + (if seen-by-everyone? + :seen-by-everyone + status))]] + [touchable-highlight + {:on-press (fn [] + (dispatch [:show-message-details {:message-status status + :user-statuses user-statuses + :participants participants}]))} + [view st/delivery-view + (for [[_ {:keys [whisper-identity]}] (take 3 user-statuses)] + ^{:key whisper-identity} + [image {:source {:uri (or (get-in contacts [whisper-identity :photo-path]) + (identicon whisper-identity))} + :style {:width 16 + :height 16 + :borderRadius 8}}]) + (if (> (count user-statuses) 3) + [text {:style st/delivery-text + :font :default} + (str "+ " (- (count user-statuses) 3))])]]))) + +(defview message-delivery-status [{:keys [message-id chat-id message-status user-statuses]}] + [app-db-message-status-value [:get-in [:message-statuses message-id :status]]] + (let [delivery-status (get-in user-statuses [chat-id :status]) + status (if (cu/console? chat-id) + :seen + (or delivery-status message-status app-db-message-status-value :sending))] + [view st/delivery-view + [image {:source (case status + :seen {:uri :icon_ok_small} + :failed res/delivery-failed-icon + nil) + :style st/delivery-image}] + [text {:style st/delivery-text + :font :default} + (message-status-label status)]])) (defview member-photo [from] [photo-path [:photo-path from]] @@ -167,14 +217,16 @@ content ;; TODO show for last or selected (when (and selected delivery-status) - [message-delivery-status {:delivery-status delivery-status}])]]])) + [message-delivery-status message])]]])) (defn message-body - [{:keys [outgoing] :as message} content] + [{:keys [outgoing message-type] :as message} content] [view (st/message-body message) content (when outgoing - [message-delivery-status message])]) + (if (= (keyword message-type) :group-user-message) + [group-message-delivery-status message] + [message-delivery-status message]))]) (defn message-container-animation-logic [{:keys [to-value val callback]}] (fn [_] @@ -210,19 +262,18 @@ children)])})) (into [view] children))) -(defn chat-message [{:keys [outgoing delivery-status timestamp new-day group-chat message-id chat-id] - :as message}] - (let [status (subscribe [:get-in [:message-status chat-id message-id]])] +(defn chat-message [{:keys [outgoing message-id chat-id user-statuses]}] + (let [my-identity (api/my-identity) + status (subscribe [:get-in [:message-user-statuses message-id my-identity]])] (r/create-class {:component-did-mount (fn [] (when (and (not outgoing) - (not= :seen delivery-status) - (not= :seen @status)) + (not= :seen (keyword @status)) + (not= :seen (keyword (get-in user-statuses [my-identity :status])))) (dispatch [:send-seen! chat-id message-id]))) :reagent-render - (fn [{:keys [outgoing delivery-status timestamp new-day group-chat] - :as message}] + (fn [{:keys [outgoing timestamp new-day group-chat] :as message}] [message-container message ;; TODO there is no new-day info in message (when new-day @@ -233,5 +284,4 @@ (if incoming-group incoming-group-message-body message-body) - (merge message {:delivery-status (keyword delivery-status) - :incoming-group incoming-group})])]])}))) + (merge message {:incoming-group incoming-group})])]])}))) diff --git a/src/status_im/chats_list/screen.cljs b/src/status_im/chats_list/screen.cljs index 5fabe19776..cf2ad8c381 100644 --- a/src/status_im/chats_list/screen.cljs +++ b/src/status_im/chats_list/screen.cljs @@ -9,7 +9,6 @@ image touchable-highlight]] [status-im.utils.listview :refer [to-datasource]] - [reagent.core :as r] [status-im.chats-list.views.chat-list-item :refer [chat-list-item]] [status-im.components.action-button :refer [action-button action-button-item]] diff --git a/src/status_im/components/status_bar.cljs b/src/status_im/components/status_bar.cljs index 347b35559f..e34ce4f0ba 100644 --- a/src/status_im/components/status_bar.cljs +++ b/src/status_im/components/status_bar.cljs @@ -1,6 +1,5 @@ (ns status-im.components.status-bar (:require [status-im.components.react :as ui] - [status-im.components.styles :as cst] [status-im.utils.platform :refer [platform-specific]])) (defn status-bar [{type :type diff --git a/src/status_im/contacts/subs.cljs b/src/status_im/contacts/subs.cljs index 73040040c3..9705216f62 100644 --- a/src/status_im/contacts/subs.cljs +++ b/src/status_im/contacts/subs.cljs @@ -6,7 +6,7 @@ (register-sub :get-contacts (fn [db _] (let [contacts (reaction (:contacts @db))] - (reaction (vals @contacts))))) + (reaction @contacts)))) (defn sort-contacts [contacts] (sort-by :name #(compare (clojure.string/lower-case %1) diff --git a/src/status_im/contacts/validations.cljs b/src/status_im/contacts/validations.cljs index ea78ecdaab..ec3ab79dcf 100644 --- a/src/status_im/contacts/validations.cljs +++ b/src/status_im/contacts/validations.cljs @@ -7,7 +7,7 @@ (.isAddress js/Web3.prototype s)) (defn unique-identity? [identity] - (not (realm/exists? :account :contact :whisper-identity identity))) + (not (realm/exists? :account :contact {:whisper-identity identity}))) (defn valid-length? [identity] (let [length (count identity)] diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index 63f08004cc..3402f69ad4 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -35,7 +35,8 @@ :contacts-ids #{} :selected-contacts #{} :chats-updated-signal 0 - :show-actions false + :chat-ui-props {:show-actions? false + :show-bottom-info? false} :selected-participants #{} :signed-up false :view-id default-view diff --git a/src/status_im/discovery/model.cljs b/src/status_im/discovery/model.cljs index e1e0cf0a9f..16ba2cbb1e 100644 --- a/src/status_im/discovery/model.cljs +++ b/src/status_im/discovery/model.cljs @@ -39,7 +39,7 @@ (defn discovery-list [] (->> (-> (r/get-all :account :discovery) (r/sorted :priority :desc) - (r/collection->map)) + (r/realm-collection->list)) (mapv #(update % :tags vals)))) (defn- add-discoveries [discoveries] @@ -65,5 +65,5 @@ (defn all-tags [] (-> (r/get-all :account :tag) (r/sorted :count :desc) - r/collection->map)) + r/realm-collection->list)) diff --git a/src/status_im/models/accounts.cljs b/src/status_im/models/accounts.cljs index bba9684d65..112ebfb464 100644 --- a/src/status_im/models/accounts.cljs +++ b/src/status_im/models/accounts.cljs @@ -3,7 +3,7 @@ (defn get-accounts [] (-> (r/get-all :base :account) - r/collection->map)) + r/realm-collection->list)) (defn save-account [update?] #(r/create :base :account % update?)) diff --git a/src/status_im/models/chats.cljs b/src/status_im/models/chats.cljs index 265f80ca85..7a542d3b4c 100644 --- a/src/status_im/models/chats.cljs +++ b/src/status_im/models/chats.cljs @@ -25,7 +25,7 @@ chat-id)) (defn chat-exists? [chat-id] - (r/exists? :account :chat :chat-id chat-id)) + (r/exists? :account :chat {:chat-id chat-id})) (defn add-status-message [chat-id] ;; TODO Get real status @@ -39,29 +39,6 @@ :content-type content-type-status :outgoing false})) -(defn create-chat - ([{:keys [last-message-id] :as chat}] - (let [chat (assoc chat :last-message-id (or last-message-id ""))] - (r/write :account #(r/create :account :chat chat true)))) - ([db chat-id identities group-chat? chat-name] - (when-not (chat-exists? chat-id) - (let [chat-name (or chat-name - (get-chat-name chat-id identities)) - _ (log/debug "creating chat" chat-name)] - (r/write :account - (fn [] - (let [contacts (mapv (fn [ident] - {:identity ident}) identities)] - (r/create :account :chat - {:chat-id chat-id - :is-active true - :name chat-name - :group-chat group-chat? - :timestamp (timestamp) - :contacts contacts - :last-message-id ""})))) - (add-status-message chat-id))))) - (defn chat-contacts [chat-id] (-> (r/get-by-field :account :chat :chat-id chat-id) (r/single) @@ -93,7 +70,7 @@ (defn chats-list [] (-> (r/get-all :account :chat) (r/sorted :timestamp :desc) - r/collection->map + r/realm-collection->list normalize-contacts)) (defn chat-by-id [chat-id] @@ -101,6 +78,37 @@ (r/single-cljs) (r/list-to-array :contacts))) +(defn update-chat [{:keys [last-message-id chat-id] :as chat}] + (let [{old-chat-id :chat-id + :as old-chat} (chat-by-id chat-id)] + (when old-chat-id + (let [chat (-> (merge old-chat chat) + (assoc chat :last-message-id (or last-message-id "")))] + (r/write :account #(r/create :account :chat chat true)))))) + +(defn create-chat + ([{:keys [last-message-id] :as chat}] + (let [chat (assoc chat :last-message-id (or last-message-id ""))] + (r/write :account #(r/create :account :chat chat true)))) + ([db chat-id identities group-chat? chat-name] + (when-not (chat-exists? chat-id) + (let [chat-name (or chat-name + (get-chat-name chat-id identities)) + _ (log/debug "creating chat" chat-name)] + (r/write :account + (fn [] + (let [contacts (mapv (fn [ident] + {:identity ident}) identities)] + (r/create :account :chat + {:chat-id chat-id + :is-active true + :name chat-name + :group-chat group-chat? + :timestamp (timestamp) + :contacts contacts + :last-message-id ""})))) + (add-status-message chat-id))))) + (defn chat-add-participants [chat-id identities] (r/write :account (fn [] diff --git a/src/status_im/models/contacts.cljs b/src/status_im/models/contacts.cljs index f6859f9788..c67320b363 100644 --- a/src/status_im/models/contacts.cljs +++ b/src/status_im/models/contacts.cljs @@ -8,7 +8,7 @@ (defn get-contacts [] (-> (r/get-all :account :contact) (r/sorted :name :asc) - r/collection->map)) + r/realm-collection->list)) (defn get-contact [id] (r/get-one-by-field :account :contact :whisper-identity id)) diff --git a/src/status_im/models/messages.cljs b/src/status_im/models/messages.cljs index be64a2c725..601c4af89d 100644 --- a/src/status_im/models/messages.cljs +++ b/src/status_im/models/messages.cljs @@ -17,6 +17,13 @@ [s] (keywordize-keys (apply hash-map (split s #"[;=]")))) +(defn- user-statuses-to-map + [user-statuses] + (->> (vals user-statuses) + (mapv (fn [{:keys [whisper-identity] :as status}] + [whisper-identity status])) + (into {}))) + (def default-values {:outgoing false :to nil @@ -26,10 +33,9 @@ (defn save-message ;; todo remove chat-id parameter - [chat-id {:keys [delivery-status message-id content] - :or {delivery-status :sending} + [chat-id {:keys [message-id content] :as message}] - (when-not (r/exists? :account :message :message-id message-id) + (when-not (r/exists? :account :message {:message-id message-id}) (r/write :account (fn [] (let [content' (if (string? content) @@ -39,7 +45,6 @@ message {:chat-id chat-id :content content' - :delivery-status delivery-status :timestamp (timestamp)})] (r/create :account :message message' true)))))) @@ -54,26 +59,29 @@ (->> (-> (r/get-by-field :account :message :chat-id chat-id) (r/sorted :timestamp :desc) (r/page from (+ from c/default-number-of-messages)) - (r/collection->map)) + (r/realm-collection->list)) + (mapv #(update % :user-statuses user-statuses-to-map)) (into '()) reverse (keep (fn [{:keys [content-type preview] :as message}] - (if (command-type? content-type) - (-> message - (update :content str-to-map) - (assoc :rendered-preview - (when preview - (generate-hiccup (read-string preview))))) - message)))))) + (if (command-type? content-type) + (-> message + (update :content str-to-map) + (assoc :rendered-preview + (when preview + (generate-hiccup (read-string preview))))) + message)))))) (defn update-message! [{:keys [message-id] :as message}] (r/write :account (fn [] - (when (r/exists? :account :message :message-id message-id) - (r/create :account :message message true))))) + (when (r/exists? :account :message {:message-id message-id}) + (let [message (update message :user-statuses vals)] + (r/create :account :message message true)))))) (defn get-message [id] - (r/get-one-by-field :account :message :message-id id)) + (some-> (r/get-one-by-field :account :message :message-id id) + (update :user-statuses user-statuses-to-map))) (defn get-last-message [chat-id] (-> (r/get-by-field :account :message :chat-id chat-id) diff --git a/src/status_im/models/pending_messages.cljs b/src/status_im/models/pending_messages.cljs index 2f3d21af22..25e08f8e60 100644 --- a/src/status_im/models/pending_messages.cljs +++ b/src/status_im/models/pending_messages.cljs @@ -15,7 +15,7 @@ [:status :sent] [:status :failed]]) (r/sorted :timestamp :desc) - (r/collection->map))] + (r/realm-collection->list))] (->> collection (map (fn [{:keys [message-id] :as message}] (let [message (-> message diff --git a/src/status_im/models/requests.cljs b/src/status_im/models/requests.cljs index 8dde634ecd..641e8a28f1 100644 --- a/src/status_im/models/requests.cljs +++ b/src/status_im/models/requests.cljs @@ -3,7 +3,7 @@ (defn get-requests [] (-> (r/get-all :account :request) - r/collection->map)) + r/realm-collection->list)) (defn create-request [request] (r/create :account :request request true)) diff --git a/src/status_im/persistence/realm/core.cljs b/src/status_im/persistence/realm/core.cljs index 9088c6e308..9e0b2bfca3 100644 --- a/src/status_im/persistence/realm/core.cljs +++ b/src/status_im/persistence/realm/core.cljs @@ -168,8 +168,8 @@ (defn delete [schema obj] (.delete (realm schema) obj)) -(defn exists? [schema schema-name field value] - (pos? (.-length (get-by-field schema schema-name field value)))) +(defn exists? [schema schema-name fields] + (pos? (.-length (get-by-fields schema schema-name :and fields)))) (defn get-count [objs] (.-length objs)) @@ -177,7 +177,7 @@ (defn get-list [schema schema-name] (vals (js->clj (.objects (realm schema) (to-string schema-name)) :keywordize-keys true))) -(defn collection->map [collection] +(defn realm-collection->list [collection] (-> (.map collection (fn [object _ _] object)) (js->clj :keywordize-keys true))) diff --git a/src/status_im/persistence/realm/schemas.cljs b/src/status_im/persistence/realm/schemas.cljs index caba7e6404..91dbe0e0e1 100644 --- a/src/status_im/persistence/realm/schemas.cljs +++ b/src/status_im/persistence/realm/schemas.cljs @@ -52,30 +52,38 @@ :primaryKey :key :properties {:key "string" :value "string"}} + {:name :user-status + :primaryKey :id + :properties {:id "string" + :whisper-identity {:type "string" + :default ""} + :status "string"}} {:name :message :primaryKey :message-id - :properties {:message-id "string" - :from "string" - :to {:type "string" - :optional true} - :group-id {:type "string" - :optional true} - :content "string" ;; TODO make it ArrayBuffer - :content-type "string" - :timestamp "int" - :chat-id {:type "string" - :indexed true} - :outgoing "bool" - :delivery-status {:type "string" - :optional true} - :retry-count {:type :int - :default 0} - :same-author "bool" - :same-direction "bool" - :preview {:type :string - :optional true} - :message-type {:type :string - :optional true}}} + :properties {:message-id "string" + :from "string" + :to {:type "string" + :optional true} + :group-id {:type "string" + :optional true} + :content "string" ;; TODO make it ArrayBuffer + :content-type "string" + :timestamp "int" + :chat-id {:type "string" + :indexed true} + :outgoing "bool" + :retry-count {:type :int + :default 0} + :same-author "bool" + :same-direction "bool" + :preview {:type :string + :optional true} + :message-type {:type :string + :optional true} + :message-status {:type :string + :optional true} + :user-statuses {:type :list + :objectType "user-status"}}} {:name :pending-message :primaryKey :message-id :properties {:message-id "string" diff --git a/src/status_im/persistence/simple_kv_store.cljs b/src/status_im/persistence/simple_kv_store.cljs index 84151ef522..01ef851565 100644 --- a/src/status_im/persistence/simple_kv_store.cljs +++ b/src/status_im/persistence/simple_kv_store.cljs @@ -16,7 +16,7 @@ (r/single-cljs) (r/decode-value))) (contains-key? [_ key] - (r/exists? schema :kv-store :key key)) + (r/exists? schema :kv-store {:key key})) (delete [_ key] (r/write schema (fn [] diff --git a/src/status_im/protocol/handlers.cljs b/src/status_im/protocol/handlers.cljs index bfbabc4976..f7fa056b50 100644 --- a/src/status_im/protocol/handlers.cljs +++ b/src/status_im/protocol/handlers.cljs @@ -15,7 +15,8 @@ [status-im.models.protocol :refer [update-identity set-initialized]] [status-im.constants :refer [text-content-type]] - [status-im.i18n :refer [label]])) + [status-im.i18n :refer [label]] + [status-im.utils.random :as random])) (register-handler :initialize-protocol (u/side-effect! @@ -104,49 +105,54 @@ (log/debug action message-id from group-id identity) (participant-invited-to-group-message group-id identity from message-id)))) -(defn update-message! [status] - (fn [_ [_ _ message-id]] - (messages/update-message! {:message-id message-id - :delivery-status status}))) +(defn save-message-status! [status] + (fn [_ [_ {:keys [message-id whisper-identity]}]] + (when-let [message (messages/get-message message-id)] + (let [message (if whisper-identity + (update-in message + [:user-statuses whisper-identity] + (fn [{old-status :status}] + {:id (random/id) + :whisper-identity whisper-identity + :status (if (= (keyword old-status) :seen) + old-status + status)})) + (assoc message :message-status status))] + (messages/update-message! message))))) (defn update-message-status [status] - (fn [db [_ chat-id message-id]] - (let [current-status (get-in db [:message-status chat-id message-id])] + (fn [db [_ {:keys [message-id whisper-identity]}]] + (let [db-key (if whisper-identity + [:message-user-statuses message-id whisper-identity] + [:message-statuses message-id]) + current-status (get-in db db-key)] (if-not (= :seen current-status) - (assoc-in db [:message-status chat-id message-id] status) + (assoc-in db db-key {:whisper-identity whisper-identity + :status status}) db)))) -(register-handler :message-delivered - (after (update-message! :delivered)) - (update-message-status :delivered)) - (register-handler :message-failed - (after (update-message! :failed)) + (after (save-message-status! :failed)) (update-message-status :failed)) (register-handler :message-sent - (after (update-message! :sent)) + (after (save-message-status! :sent)) (update-message-status :sent)) +(register-handler :message-delivered + (after (save-message-status! :delivered)) + (update-message-status :delivered)) + (register-handler :message-seen - [(after (update-message! :seen)) - (after (fn [_ [_ chat-id]] + [(after (save-message-status! :seen)) + (after (fn [_ [_ {:keys [chat-id]}]] (dispatch [:remove-unviewed-messages chat-id])))] (update-message-status :seen)) (register-handler :pending-message-upsert - (after - (fn [_ [_ {:keys [message-id status] :as pending-message}]] - (pending-messages/upsert-pending-message! pending-message) - (messages/update-message! {:message-id message-id - :delivery-status status}))) - (fn [db [_ {:keys [message-id chat-id status] :as pending-message}]] - (if chat-id - (let [current-status (get-in db [:message-status chat-id message-id])] - (if-not (= :seen current-status) - (assoc-in db [:message-status chat-id message-id] status) - db)) - db))) + (u/side-effect! + (fn [_ [_ pending-message]] + (pending-messages/upsert-pending-message! pending-message)))) (register-handler :pending-message-remove (u/side-effect! diff --git a/src/status_im/protocol/protocol_handler.cljs b/src/status_im/protocol/protocol_handler.cljs index c4413e2fcd..f330850218 100644 --- a/src/status_im/protocol/protocol_handler.cljs +++ b/src/status_im/protocol/protocol_handler.cljs @@ -23,14 +23,18 @@ :to to)])) :contact-request (let [{:keys [from payload]} event] (dispatch [:contact-request-received (assoc payload :from from)])) - :message-delivered (let [{:keys [message-id from]} event] - (dispatch [:message-delivered from message-id])) - :message-seen (let [{:keys [message-id from]} event] - (dispatch [:message-seen from message-id])) - :message-failed (let [{:keys [message-id chat-id] :as event} event] - (dispatch [:message-failed chat-id message-id])) - :message-sent (let [{:keys [message-id chat-id]} event] - (dispatch [:message-sent chat-id message-id])) + :message-delivered (let [{:keys [from message-id]} event] + (dispatch [:message-delivered {:whisper-identity from + :message-id message-id}])) + :message-seen (let [{:keys [from message-id]} event] + (dispatch [:message-seen {:whisper-identity from + :message-id message-id}])) + :message-failed (let [{:keys [chat-id message-id]} event] + (dispatch [:message-failed {:chat-id chat-id + :message-id message-id}])) + :message-sent (let [{:keys [chat-id message-id] :as data} event] + (dispatch [:message-sent {:chat-id chat-id + :message-id message-id}])) :user-discovery-keypair (let [{:keys [from]} event] (dispatch [:contact-keypair-received from])) :pending-message-upsert (let [{message :message} event]