Merge pull request #223 from status-im/feature/group-chat-delivery-status-#205

Group chat delivery statuses (#205)

Former-commit-id: 21963e47de
This commit is contained in:
Roman Volosovskyi 2016-09-16 14:50:25 +03:00 committed by GitHub
commit d234817599
30 changed files with 425 additions and 190 deletions

View File

@ -8,9 +8,8 @@
[reagent "0.5.1" :exclusions [cljsjs/react]] [reagent "0.5.1" :exclusions [cljsjs/react]]
[re-frame "0.7.0"] [re-frame "0.7.0"]
[prismatic/schema "1.0.4"] [prismatic/schema "1.0.4"]
^{:voom {:repo "git@github.com:status-im/status-lib.git" ^{:voom {:repo "git@github.com:status-im/status-lib.git" :branch "group-chat-statuses"}}
:branch "master"}} [status-im/protocol "0.2.3-20160914_155558-gfed628a"]
[status-im/protocol "0.2.2-20160909_082306-gcfbb92b"]
[natal-shell "0.3.0"] [natal-shell "0.3.0"]
[com.andrewmcveigh/cljs-time "0.4.0"] [com.andrewmcveigh/cljs-time "0.4.0"]
[tailrecursion/cljs-priority-map "1.2.0"] [tailrecursion/cljs-priority-map "1.2.0"]

View File

@ -36,9 +36,19 @@
status-im.chat.handlers.wallet-chat status-im.chat.handlers.wallet-chat
[status-im.utils.logging :as log])) [status-im.utils.logging :as log]))
(register-handler :set-show-actions (register-handler :set-chat-ui-props
(fn [db [_ show-actions]] (fn [db [_ ui-element value]]
(assoc db :show-actions show-actions))) (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 (register-handler :load-more-messages
(fn [{:keys [current-chat-id loading-allowed] :as db} _] (fn [{:keys [current-chat-id loading-allowed] :as db} _]
@ -296,7 +306,6 @@
[{:keys [new-chat]} _] [{:keys [new-chat]} _]
(chats/create-chat new-chat)) (chats/create-chat new-chat))
(defn open-chat! (defn open-chat!
[_ [_ chat-id _ navigation-type]] [_ [_ chat-id _ navigation-type]]
(dispatch [(or navigation-type :navigate-to) :chat chat-id])) (dispatch [(or navigation-type :navigate-to) :chat chat-id]))
@ -395,11 +404,10 @@
(fn [db [_ h]] (fn [db [_ h]]
(assoc db :layout-height h))) (assoc db :layout-height h)))
(register-handler :send-seen! (register-handler :send-seen!
(after (fn [_ [_ chat-id message-id]] (after (fn [_ [_ chat-id message-id]]
(when-not (console? chat-id)) (dispatch [:message-seen {:message-id message-id
(dispatch [:message-seen chat-id message-id]))) :chat-id chat-id}])))
(u/side-effect! (u/side-effect!
(fn [_ [_ chat-id message-id]] (fn [_ [_ chat-id message-id]]
(when-not (console? chat-id) (when-not (console? chat-id)
@ -419,16 +427,16 @@
(fn [db [_ chat-id mode]] (fn [db [_ chat-id mode]]
(assoc-in db [:kb-mode chat-id] mode))) (assoc-in db [:kb-mode chat-id] mode)))
(defn save-chat! (defn update-chat!
[_ [_ chat]] [_ [_ chat]]
(chats/create-chat chat)) (chats/update-chat chat))
(register-handler :update-chat! (register-handler :update-chat!
(-> (fn [db [_ {:keys [chat-id] :as chat}]] (-> (fn [db [_ {:keys [chat-id] :as chat}]]
(if (get-in db [:chats chat-id]) (if (get-in db [:chats chat-id])
(update-in db [:chats chat-id] merge chat) (update-in db [:chats chat-id] merge chat)
db)) db))
((after save-chat!)))) ((after update-chat!))))
(register-handler :check-autorun (register-handler :check-autorun
(u/side-effect! (u/side-effect!

View File

@ -6,7 +6,8 @@
[status-im.commands.utils :refer [generate-hiccup]] [status-im.commands.utils :refer [generate-hiccup]]
[status-im.constants :refer [content-type-command-request]] [status-im.constants :refer [content-type-command-request]]
[cljs.reader :refer [read-string]] [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}] (defn check-preview [{:keys [content] :as message}]
(if-let [preview (:preview content)] (if-let [preview (:preview content)]
@ -34,7 +35,6 @@
message' (assoc (->> message message' (assoc (->> message
(cu/check-author-direction previous-message) (cu/check-author-direction previous-message)
(check-preview)) (check-preview))
:delivery-status :sending
:chat-id chat-id')] :chat-id chat-id')]
(store-message message') (store-message message')
(when-not (c/chat-exists? chat-id') (when-not (c/chat-exists? chat-id')

View File

@ -24,10 +24,10 @@
[{:keys [current-chat-id] :as db} [_ chat-id]] [{:keys [current-chat-id] :as db} [_ chat-id]]
(let [chat-id' (or chat-id current-chat-id) (let [chat-id' (or chat-id current-chat-id)
requests (-> ;; todo maybe limit is needed 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"]]) [:status "open"]])
(realm/sorted :added :desc) (realm/sorted :added :desc)
(realm/collection->map)) (realm/realm-collection->list))
requests' (map #(update % :type keyword) requests)] requests' (map #(update % :type keyword) requests)]
(assoc-in db [:chats chat-id' :requests] requests'))) (assoc-in db [:chats chat-id' :requests] requests')))

View File

@ -16,11 +16,6 @@
[status-im.protocol.api :as api] [status-im.protocol.api :as api]
[status-im.utils.logging :as log])) [status-im.utils.logging :as log]))
(defn default-delivery-status [chat-id]
(if (cu/console? chat-id)
:seen
:sending))
(defn prepare-message (defn prepare-message
[{:keys [identity current-chat-id] :as db} _] [{:keys [identity current-chat-id] :as db} _]
(let [text (get-in db [:chats current-chat-id :input-text]) (let [text (get-in db [:chats current-chat-id :input-text])
@ -33,7 +28,6 @@
:to current-chat-id :to current-chat-id
:from identity :from identity
:content-type text-content-type :content-type text-content-type
:delivery-status (default-delivery-status current-chat-id)
:outgoing true :outgoing true
:timestamp (time/now-ms)})] :timestamp (time/now-ms)})]
(if command (if command
@ -49,7 +43,6 @@
:to chat-id :to chat-id
:content (assoc content :preview preview-string) :content (assoc content :preview preview-string)
:content-type content-type-command :content-type content-type-command
:delivery-status (default-delivery-status chat-id)
:outgoing true :outgoing true
:preview preview-string :preview preview-string
:rendered-preview preview :rendered-preview preview
@ -162,7 +155,6 @@
:content message :content message
:from identity :from identity
:content-type text-content-type :content-type text-content-type
:delivery-status (default-delivery-status chat-id)
:outgoing true :outgoing true
:timestamp (time/now-ms)}) :timestamp (time/now-ms)})
message'' (if group-chat message'' (if group-chat

View File

@ -1,12 +1,13 @@
(ns status-im.chat.handlers.unviewed-messages (ns status-im.chat.handlers.unviewed-messages
(:require [re-frame.core :refer [after enrich path dispatch]] (:require [re-frame.core :refer [after enrich path dispatch]]
[status-im.utils.handlers :refer [register-handler]] [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 [] (defn delivered-messages []
(-> (realm/get-by-fields :account :message :and [[:delivery-status :delivered] (-> (realm/get-by-fields :account :message :and {:outgoing false
[:outgoing false]]) :message-status nil})
(realm/collection->map))) (realm/realm-collection->list)))
(defn set-unviewed-messages [db] (defn set-unviewed-messages [db]
(let [messages (->> (::raw-unviewed-messages db) (let [messages (->> (::raw-unviewed-messages db)

View File

@ -24,6 +24,7 @@
[status-im.chat.views.response :refer [response-view]] [status-im.chat.views.response :refer [response-view]]
[status-im.chat.views.new-message :refer [chat-message-new]] [status-im.chat.views.new-message :refer [chat-message-new]]
[status-im.chat.views.actions :refer [actions-view]] [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.i18n :refer [label label-pluralize]]
[status-im.components.animation :as anim] [status-im.components.animation :as anim]
[reagent.core :as r] [reagent.core :as r]
@ -101,7 +102,7 @@
(defn toolbar-content [] (defn toolbar-content []
(let [{:keys [group-chat name contacts chat-id]} (subscribe [:chat-properties [:group-chat :name :contacts :chat-id]]) (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]])] contact (subscribe [:get-in [:contacts @chat-id]])]
(fn [] (fn []
[view (st/chat-name-view @show-actions) [view (st/chat-name-view @show-actions)
@ -123,20 +124,20 @@
(online-text @contact @chat-id)])]))) (online-text @contact @chat-id)])])))
(defn toolbar-action [] (defn toolbar-action []
(let [show-actions (subscribe [:show-actions])] (let [show-actions (subscribe [:chat-ui-props :show-actions?])]
(fn [] (fn []
(if @show-actions (if @show-actions
[touchable-highlight [touchable-highlight
{:on-press #(dispatch [:set-show-actions false])} {:on-press #(dispatch [:set-chat-ui-props :show-actions? false])}
[view st/action [view st/action
[icon :up st/up-icon]]] [icon :up st/up-icon]]]
[touchable-highlight [touchable-highlight
{:on-press #(dispatch [:set-show-actions true])} {:on-press #(dispatch [:set-chat-ui-props :show-actions? true])}
[view st/action [view st/action
[chat-icon]]])))) [chat-icon]]]))))
(defview chat-toolbar [] (defview chat-toolbar []
[show-actions [:show-actions]] [show-actions [:chat-ui-props :show-actions?]]
[view [view
[status-bar] [status-bar]
[toolbar {:hide-nav? show-actions [toolbar {:hide-nav? show-actions
@ -182,7 +183,8 @@
(defn chat [] (defn chat []
(let [group-chat (subscribe [:chat :group-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?]) command? (subscribe [:command?])
layout-height (subscribe [:get :layout-height])] layout-height (subscribe [:get :layout-height])]
(r/create-class (r/create-class
@ -200,6 +202,10 @@
;; todo uncomment this ;; todo uncomment this
#_(when @group-chat [typing-all]) #_(when @group-chat [typing-all])
[response-view] [response-view]
(when-not @command? [suggestion-container]) (when-not @command?
[suggestion-container])
[chat-message-new] [chat-message-new]
(when @show-actions [actions-view])])}))) (when @show-actions?
[actions-view])
(when @show-bottom-info?
[bottom-info-view])])})))

View File

@ -157,7 +157,6 @@
(def intro-status (def intro-status
{:message-id "intro-status" {:message-id "intro-status"
:content (label :t/intro-status) :content (label :t/intro-status)
:delivery-status "seen"
:from "console" :from "console"
:chat-id "console" :chat-id "console"
:content-type content-type-status :content-type content-type-status

View File

@ -2,13 +2,15 @@
(:require [status-im.components.styles :refer [font (:require [status-im.components.styles :refer [font
title-font title-font
color-white color-white
color-black
chat-background chat-background
online-color online-color
selected-message-color selected-message-color
separator-color separator-color
text1-color text1-color
text2-color text2-color
toolbar-background1]])) toolbar-background1]]
[status-im.utils.logging :as log]))
(def chat-view (def chat-view
{:flex 1 {:flex 1
@ -114,6 +116,13 @@
:color text2-color :color text2-color
:font-size 12}) :font-size 12})
(def actions-overlay
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0})
(def typing-all (def typing-all
{:marginBottom 20}) {:marginBottom 20})
@ -137,12 +146,51 @@
:fontFamily font :fontFamily font
:color text2-color}) :color text2-color})
(def actions-overlay
{:position :absolute
:top 0
:bottom 0
:left 0
:right 0})
(def overlay-highlight (def overlay-highlight
{:flex 1}) {: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"})

View File

@ -3,6 +3,7 @@
(:require [re-frame.core :refer [register-sub dispatch subscribe path]] (:require [re-frame.core :refer [register-sub dispatch subscribe path]]
[status-im.utils.platform :refer [ios?]] [status-im.utils.platform :refer [ios?]]
[status-im.models.commands :as commands] [status-im.models.commands :as commands]
[status-im.models.chats :as chats]
[status-im.constants :refer [response-suggesstion-resize-duration]] [status-im.constants :refer [response-suggesstion-resize-duration]]
[status-im.chat.constants :as c] [status-im.chat.constants :as c]
[status-im.handlers.content-suggestions :refer [get-content-suggestions]] [status-im.handlers.content-suggestions :refer [get-content-suggestions]]
@ -19,9 +20,9 @@
(into {})))) (into {}))))
(register-sub (register-sub
:show-actions :chat-ui-props
(fn [db _] (fn [db [_ ui-element]]
(reaction (:show-actions @db)))) (reaction (get-in @db [:chat-ui-props ui-element]))))
(register-sub :chat (register-sub :chat
(fn [db [_ k]] (fn [db [_ k]]
@ -48,6 +49,10 @@
(fn [db _] (fn [db _]
(reaction (commands/get-commands @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 (register-sub :get-responses
(fn [db _] (fn [db _]
(let [current-chat (@db :current-chat-id)] (let [current-chat (@db :current-chat-id)]

View File

@ -105,7 +105,7 @@
subtitle] subtitle]
icon-name :icon}] icon-name :icon}]
[touchable-highlight {:on-press (fn [] [touchable-highlight {:on-press (fn []
(dispatch [:set-show-actions false]) (dispatch [:set-chat-ui-props :show-actions? false])
(when handler (when handler
(handler)))} (handler)))}
[view st/action-icon-row [view st/action-icon-row
@ -138,5 +138,5 @@
^{:key action} [action-view action]))]]))) ^{:key action} [action-view action]))]])))
(defn actions-view [] (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]]) [actions-list-view]])

View File

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

View File

@ -12,6 +12,7 @@
[status-im.components.animation :as anim] [status-im.components.animation :as anim]
[status-im.chat.views.request-message :refer [message-content-command-request]] [status-im.chat.views.request-message :refer [message-content-command-request]]
[status-im.chat.styles.message :as st] [status-im.chat.styles.message :as st]
[status-im.models.chats :refer [chat-by-id]]
[status-im.models.commands :refer [parse-command-message-content [status-im.models.commands :refer [parse-command-message-content
parse-command-request]] parse-command-request]]
[status-im.resources :as res] [status-im.resources :as res]
@ -20,7 +21,10 @@
content-type-status content-type-status
content-type-command content-type-command
content-type-command-request]] 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] (defn message-date [timestamp]
[view {} [view {}
@ -131,18 +135,64 @@
[message-content-audio {:content content [message-content-audio {:content content
:content-type content-type}]]]) :content-type content-type}]]])
(defview message-delivery-status [{:keys [delivery-status chat-id message-id] :as message}] (defview group-message-delivery-status [{:keys [message-id group-id message-status user-statuses] :as msg}]
[status [:get-in [:message-status chat-id message-id]]] [app-db-message-user-statuses [:get-in [:message-user-statuses message-id]]
[view st/delivery-view app-db-message-status-value [:get-in [:message-statuses message-id :status]]
[image {:source (case (or status delivery-status) chat [:get-chat-by-id group-id]
:seen {:uri :icon_ok_small} contacts [:get-contacts]]
:seen-by-everyone {:uri :icon_ok_small} (let [status (or message-status app-db-message-status-value :sending)
:failed res/delivery-failed-icon user-statuses (merge user-statuses app-db-message-user-statuses)
nil) participants (:contacts chat)
:style st/delivery-image}] seen-by-everyone? (and (= (count user-statuses) (count participants))
[text {:style st/delivery-text (every? (fn [[_ {:keys [status]}]]
:font :default} (= (keyword status) :seen)) user-statuses))]
(message-status-label (or status delivery-status))]]) (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] (defview member-photo [from]
[photo-path [:photo-path from]] [photo-path [:photo-path from]]
@ -167,14 +217,16 @@
content content
;; TODO show for last or selected ;; TODO show for last or selected
(when (and selected delivery-status) (when (and selected delivery-status)
[message-delivery-status {:delivery-status delivery-status}])]]])) [message-delivery-status message])]]]))
(defn message-body (defn message-body
[{:keys [outgoing] :as message} content] [{:keys [outgoing message-type] :as message} content]
[view (st/message-body message) [view (st/message-body message)
content content
(when outgoing (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]}] (defn message-container-animation-logic [{:keys [to-value val callback]}]
(fn [_] (fn [_]
@ -210,19 +262,18 @@
children)])})) children)])}))
(into [view] children))) (into [view] children)))
(defn chat-message [{:keys [outgoing delivery-status timestamp new-day group-chat message-id chat-id] (defn chat-message [{:keys [outgoing message-id chat-id user-statuses]}]
:as message}] (let [my-identity (api/my-identity)
(let [status (subscribe [:get-in [:message-status chat-id message-id]])] status (subscribe [:get-in [:message-user-statuses message-id my-identity]])]
(r/create-class (r/create-class
{:component-did-mount {:component-did-mount
(fn [] (fn []
(when (and (not outgoing) (when (and (not outgoing)
(not= :seen delivery-status) (not= :seen (keyword @status))
(not= :seen @status)) (not= :seen (keyword (get-in user-statuses [my-identity :status]))))
(dispatch [:send-seen! chat-id message-id]))) (dispatch [:send-seen! chat-id message-id])))
:reagent-render :reagent-render
(fn [{:keys [outgoing delivery-status timestamp new-day group-chat] (fn [{:keys [outgoing timestamp new-day group-chat] :as message}]
:as message}]
[message-container message [message-container message
;; TODO there is no new-day info in message ;; TODO there is no new-day info in message
(when new-day (when new-day
@ -233,5 +284,4 @@
(if incoming-group (if incoming-group
incoming-group-message-body incoming-group-message-body
message-body) message-body)
(merge message {:delivery-status (keyword delivery-status) (merge message {:incoming-group incoming-group})])]])})))
:incoming-group incoming-group})])]])})))

View File

@ -9,7 +9,6 @@
image image
touchable-highlight]] touchable-highlight]]
[status-im.utils.listview :refer [to-datasource]] [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.chats-list.views.chat-list-item :refer [chat-list-item]]
[status-im.components.action-button :refer [action-button [status-im.components.action-button :refer [action-button
action-button-item]] action-button-item]]

View File

@ -1,6 +1,5 @@
(ns status-im.components.status-bar (ns status-im.components.status-bar
(:require [status-im.components.react :as ui] (:require [status-im.components.react :as ui]
[status-im.components.styles :as cst]
[status-im.utils.platform :refer [platform-specific]])) [status-im.utils.platform :refer [platform-specific]]))
(defn status-bar [{type :type (defn status-bar [{type :type

View File

@ -6,7 +6,7 @@
(register-sub :get-contacts (register-sub :get-contacts
(fn [db _] (fn [db _]
(let [contacts (reaction (:contacts @db))] (let [contacts (reaction (:contacts @db))]
(reaction (vals @contacts))))) (reaction @contacts))))
(defn sort-contacts [contacts] (defn sort-contacts [contacts]
(sort-by :name #(compare (clojure.string/lower-case %1) (sort-by :name #(compare (clojure.string/lower-case %1)

View File

@ -7,7 +7,7 @@
(.isAddress js/Web3.prototype s)) (.isAddress js/Web3.prototype s))
(defn unique-identity? [identity] (defn unique-identity? [identity]
(not (realm/exists? :account :contact :whisper-identity identity))) (not (realm/exists? :account :contact {:whisper-identity identity})))
(defn valid-length? [identity] (defn valid-length? [identity]
(let [length (count identity)] (let [length (count identity)]

View File

@ -35,7 +35,8 @@
:contacts-ids #{} :contacts-ids #{}
:selected-contacts #{} :selected-contacts #{}
:chats-updated-signal 0 :chats-updated-signal 0
:show-actions false :chat-ui-props {:show-actions? false
:show-bottom-info? false}
:selected-participants #{} :selected-participants #{}
:signed-up false :signed-up false
:view-id default-view :view-id default-view

View File

@ -39,7 +39,7 @@
(defn discovery-list [] (defn discovery-list []
(->> (-> (r/get-all :account :discovery) (->> (-> (r/get-all :account :discovery)
(r/sorted :priority :desc) (r/sorted :priority :desc)
(r/collection->map)) (r/realm-collection->list))
(mapv #(update % :tags vals)))) (mapv #(update % :tags vals))))
(defn- add-discoveries [discoveries] (defn- add-discoveries [discoveries]
@ -65,5 +65,5 @@
(defn all-tags [] (defn all-tags []
(-> (r/get-all :account :tag) (-> (r/get-all :account :tag)
(r/sorted :count :desc) (r/sorted :count :desc)
r/collection->map)) r/realm-collection->list))

View File

@ -3,7 +3,7 @@
(defn get-accounts [] (defn get-accounts []
(-> (r/get-all :base :account) (-> (r/get-all :base :account)
r/collection->map)) r/realm-collection->list))
(defn save-account [update?] (defn save-account [update?]
#(r/create :base :account % update?)) #(r/create :base :account % update?))

View File

@ -25,7 +25,7 @@
chat-id)) chat-id))
(defn chat-exists? [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] (defn add-status-message [chat-id]
;; TODO Get real status ;; TODO Get real status
@ -39,29 +39,6 @@
:content-type content-type-status :content-type content-type-status
:outgoing false})) :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] (defn chat-contacts [chat-id]
(-> (r/get-by-field :account :chat :chat-id chat-id) (-> (r/get-by-field :account :chat :chat-id chat-id)
(r/single) (r/single)
@ -93,7 +70,7 @@
(defn chats-list [] (defn chats-list []
(-> (r/get-all :account :chat) (-> (r/get-all :account :chat)
(r/sorted :timestamp :desc) (r/sorted :timestamp :desc)
r/collection->map r/realm-collection->list
normalize-contacts)) normalize-contacts))
(defn chat-by-id [chat-id] (defn chat-by-id [chat-id]
@ -101,6 +78,37 @@
(r/single-cljs) (r/single-cljs)
(r/list-to-array :contacts))) (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] (defn chat-add-participants [chat-id identities]
(r/write :account (r/write :account
(fn [] (fn []

View File

@ -8,7 +8,7 @@
(defn get-contacts [] (defn get-contacts []
(-> (r/get-all :account :contact) (-> (r/get-all :account :contact)
(r/sorted :name :asc) (r/sorted :name :asc)
r/collection->map)) r/realm-collection->list))
(defn get-contact [id] (defn get-contact [id]
(r/get-one-by-field :account :contact :whisper-identity id)) (r/get-one-by-field :account :contact :whisper-identity id))

View File

@ -17,6 +17,13 @@
[s] [s]
(keywordize-keys (apply hash-map (split 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 (def default-values
{:outgoing false {:outgoing false
:to nil :to nil
@ -26,10 +33,9 @@
(defn save-message (defn save-message
;; todo remove chat-id parameter ;; todo remove chat-id parameter
[chat-id {:keys [delivery-status message-id content] [chat-id {:keys [message-id content]
:or {delivery-status :sending}
:as message}] :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 (r/write :account
(fn [] (fn []
(let [content' (if (string? content) (let [content' (if (string? content)
@ -39,7 +45,6 @@
message message
{:chat-id chat-id {:chat-id chat-id
:content content' :content content'
:delivery-status delivery-status
:timestamp (timestamp)})] :timestamp (timestamp)})]
(r/create :account :message message' true)))))) (r/create :account :message message' true))))))
@ -54,26 +59,29 @@
(->> (-> (r/get-by-field :account :message :chat-id chat-id) (->> (-> (r/get-by-field :account :message :chat-id chat-id)
(r/sorted :timestamp :desc) (r/sorted :timestamp :desc)
(r/page from (+ from c/default-number-of-messages)) (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 '()) (into '())
reverse 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)
(-> message (-> message
(update :content str-to-map) (update :content str-to-map)
(assoc :rendered-preview (assoc :rendered-preview
(when preview (when preview
(generate-hiccup (read-string preview))))) (generate-hiccup (read-string preview)))))
message)))))) message))))))
(defn update-message! [{:keys [message-id] :as message}] (defn update-message! [{:keys [message-id] :as message}]
(r/write :account (r/write :account
(fn [] (fn []
(when (r/exists? :account :message :message-id message-id) (when (r/exists? :account :message {:message-id message-id})
(r/create :account :message message true))))) (let [message (update message :user-statuses vals)]
(r/create :account :message message true))))))
(defn get-message [id] (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] (defn get-last-message [chat-id]
(-> (r/get-by-field :account :message :chat-id chat-id) (-> (r/get-by-field :account :message :chat-id chat-id)

View File

@ -15,7 +15,7 @@
[:status :sent] [:status :sent]
[:status :failed]]) [:status :failed]])
(r/sorted :timestamp :desc) (r/sorted :timestamp :desc)
(r/collection->map))] (r/realm-collection->list))]
(->> collection (->> collection
(map (fn [{:keys [message-id] :as message}] (map (fn [{:keys [message-id] :as message}]
(let [message (-> message (let [message (-> message

View File

@ -3,7 +3,7 @@
(defn get-requests [] (defn get-requests []
(-> (r/get-all :account :request) (-> (r/get-all :account :request)
r/collection->map)) r/realm-collection->list))
(defn create-request [request] (defn create-request [request]
(r/create :account :request request true)) (r/create :account :request request true))

View File

@ -168,8 +168,8 @@
(defn delete [schema obj] (defn delete [schema obj]
(.delete (realm schema) obj)) (.delete (realm schema) obj))
(defn exists? [schema schema-name field value] (defn exists? [schema schema-name fields]
(pos? (.-length (get-by-field schema schema-name field value)))) (pos? (.-length (get-by-fields schema schema-name :and fields))))
(defn get-count [objs] (defn get-count [objs]
(.-length objs)) (.-length objs))
@ -177,7 +177,7 @@
(defn get-list [schema schema-name] (defn get-list [schema schema-name]
(vals (js->clj (.objects (realm schema) (to-string schema-name)) :keywordize-keys true))) (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)) (-> (.map collection (fn [object _ _] object))
(js->clj :keywordize-keys true))) (js->clj :keywordize-keys true)))

View File

@ -52,30 +52,38 @@
:primaryKey :key :primaryKey :key
:properties {:key "string" :properties {:key "string"
:value "string"}} :value "string"}}
{:name :user-status
:primaryKey :id
:properties {:id "string"
:whisper-identity {:type "string"
:default ""}
:status "string"}}
{:name :message {:name :message
:primaryKey :message-id :primaryKey :message-id
:properties {:message-id "string" :properties {:message-id "string"
:from "string" :from "string"
:to {:type "string" :to {:type "string"
:optional true} :optional true}
:group-id {:type "string" :group-id {:type "string"
:optional true} :optional true}
:content "string" ;; TODO make it ArrayBuffer :content "string" ;; TODO make it ArrayBuffer
:content-type "string" :content-type "string"
:timestamp "int" :timestamp "int"
:chat-id {:type "string" :chat-id {:type "string"
:indexed true} :indexed true}
:outgoing "bool" :outgoing "bool"
:delivery-status {:type "string" :retry-count {:type :int
:optional true} :default 0}
:retry-count {:type :int :same-author "bool"
:default 0} :same-direction "bool"
:same-author "bool" :preview {:type :string
:same-direction "bool" :optional true}
:preview {:type :string :message-type {:type :string
:optional true} :optional true}
:message-type {:type :string :message-status {:type :string
:optional true}}} :optional true}
:user-statuses {:type :list
:objectType "user-status"}}}
{:name :pending-message {:name :pending-message
:primaryKey :message-id :primaryKey :message-id
:properties {:message-id "string" :properties {:message-id "string"

View File

@ -16,7 +16,7 @@
(r/single-cljs) (r/single-cljs)
(r/decode-value))) (r/decode-value)))
(contains-key? [_ key] (contains-key? [_ key]
(r/exists? schema :kv-store :key key)) (r/exists? schema :kv-store {:key key}))
(delete [_ key] (delete [_ key]
(r/write schema (r/write schema
(fn [] (fn []

View File

@ -15,7 +15,8 @@
[status-im.models.protocol :refer [update-identity [status-im.models.protocol :refer [update-identity
set-initialized]] set-initialized]]
[status-im.constants :refer [text-content-type]] [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 (register-handler :initialize-protocol
(u/side-effect! (u/side-effect!
@ -104,49 +105,54 @@
(log/debug action message-id from group-id identity) (log/debug action message-id from group-id identity)
(participant-invited-to-group-message group-id identity from message-id)))) (participant-invited-to-group-message group-id identity from message-id))))
(defn update-message! [status] (defn save-message-status! [status]
(fn [_ [_ _ message-id]] (fn [_ [_ {:keys [message-id whisper-identity]}]]
(messages/update-message! {:message-id message-id (when-let [message (messages/get-message message-id)]
:delivery-status status}))) (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] (defn update-message-status [status]
(fn [db [_ chat-id message-id]] (fn [db [_ {:keys [message-id whisper-identity]}]]
(let [current-status (get-in db [:message-status chat-id message-id])] (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) (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)))) db))))
(register-handler :message-delivered
(after (update-message! :delivered))
(update-message-status :delivered))
(register-handler :message-failed (register-handler :message-failed
(after (update-message! :failed)) (after (save-message-status! :failed))
(update-message-status :failed)) (update-message-status :failed))
(register-handler :message-sent (register-handler :message-sent
(after (update-message! :sent)) (after (save-message-status! :sent))
(update-message-status :sent)) (update-message-status :sent))
(register-handler :message-delivered
(after (save-message-status! :delivered))
(update-message-status :delivered))
(register-handler :message-seen (register-handler :message-seen
[(after (update-message! :seen)) [(after (save-message-status! :seen))
(after (fn [_ [_ chat-id]] (after (fn [_ [_ {:keys [chat-id]}]]
(dispatch [:remove-unviewed-messages chat-id])))] (dispatch [:remove-unviewed-messages chat-id])))]
(update-message-status :seen)) (update-message-status :seen))
(register-handler :pending-message-upsert (register-handler :pending-message-upsert
(after (u/side-effect!
(fn [_ [_ {:keys [message-id status] :as pending-message}]] (fn [_ [_ pending-message]]
(pending-messages/upsert-pending-message! 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)))
(register-handler :pending-message-remove (register-handler :pending-message-remove
(u/side-effect! (u/side-effect!

View File

@ -23,14 +23,18 @@
:to to)])) :to to)]))
:contact-request (let [{:keys [from payload]} event] :contact-request (let [{:keys [from payload]} event]
(dispatch [:contact-request-received (assoc payload :from from)])) (dispatch [:contact-request-received (assoc payload :from from)]))
:message-delivered (let [{:keys [message-id from]} event] :message-delivered (let [{:keys [from message-id]} event]
(dispatch [:message-delivered from message-id])) (dispatch [:message-delivered {:whisper-identity from
:message-seen (let [{:keys [message-id from]} event] :message-id message-id}]))
(dispatch [:message-seen from message-id])) :message-seen (let [{:keys [from message-id]} event]
:message-failed (let [{:keys [message-id chat-id] :as event} event] (dispatch [:message-seen {:whisper-identity from
(dispatch [:message-failed chat-id message-id])) :message-id message-id}]))
:message-sent (let [{:keys [message-id chat-id]} event] :message-failed (let [{:keys [chat-id message-id]} event]
(dispatch [:message-sent chat-id message-id])) (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] :user-discovery-keypair (let [{:keys [from]} event]
(dispatch [:contact-keypair-received from])) (dispatch [:contact-keypair-received from]))
:pending-message-upsert (let [{message :message} event] :pending-message-upsert (let [{message :message} event]