Remove chat / clear history
Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
parent
97c96bbcea
commit
9cfb591068
|
@ -342,38 +342,15 @@
|
|||
(fn [cofx [chat]]
|
||||
(models/upsert-chat chat cofx)))
|
||||
|
||||
(defn- remove-transport [chat-id {:keys [db] :as cofx}]
|
||||
(let [{:keys [group-chat public?]} (get-in db [:chats chat-id])]
|
||||
;; if this is private group chat, we have to broadcast leave and unsubscribe after that
|
||||
(if (and group-chat (not public?))
|
||||
(handlers-macro/merge-fx cofx (transport.message/send (group-chat/GroupLeave.) chat-id))
|
||||
(handlers-macro/merge-fx cofx (transport/unsubscribe-from-chat chat-id)))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:leave-chat-and-navigate-home
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [chat-id]]
|
||||
(defn remove-chat-and-navigate-home [cofx [chat-id]]
|
||||
(handlers-macro/merge-fx cofx
|
||||
(models/remove-chat chat-id)
|
||||
(navigation/replace-view :home)
|
||||
(remove-transport chat-id))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:leave-group-chat?
|
||||
[re-frame/trim-v]
|
||||
(fn [_ [chat-id]]
|
||||
{:show-confirmation {:title (i18n/label :t/leave-confirmation)
|
||||
:content (i18n/label :t/leave-group-chat-confirmation)
|
||||
:confirm-button-text (i18n/label :t/leave)
|
||||
:on-accept #(re-frame/dispatch [:leave-chat-and-navigate-home chat-id])}}))
|
||||
(navigation/replace-view :home)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:remove-chat-and-navigate-home
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [chat-id]]
|
||||
(handlers-macro/merge-fx cofx
|
||||
(models/remove-chat chat-id)
|
||||
(navigation/replace-view :home))))
|
||||
remove-chat-and-navigate-home)
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:remove-chat-and-navigate-home?
|
||||
|
@ -384,6 +361,19 @@
|
|||
:confirm-button-text (i18n/label :t/delete)
|
||||
:on-accept #(re-frame/dispatch [:remove-chat-and-navigate-home chat-id])}}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:clear-history
|
||||
(fn [{{:keys [current-chat-id]} :db :as cofx} _]
|
||||
(models/clear-history current-chat-id cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:clear-history?
|
||||
(fn [_ _]
|
||||
{:show-confirmation {:title (i18n/label :t/clear-history-confirmation)
|
||||
:content (i18n/label :t/clear-history-confirmation-content)
|
||||
:confirm-button-text (i18n/label :t/clear)
|
||||
:on-accept #(re-frame/dispatch [:clear-history])}}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:create-new-public-chat
|
||||
[re-frame/trim-v]
|
||||
|
|
|
@ -1,9 +1,21 @@
|
|||
(ns status-im.chat.models
|
||||
(:require [status-im.ui.components.styles :as styles]
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.utils.clocks :as utils.clocks]
|
||||
[status-im.transport.message.core :as transport.message]
|
||||
[status-im.data-store.chats :as chats-store]
|
||||
[status-im.transport.message.v1.group-chat :as transport.group-chat]
|
||||
[status-im.utils.handlers-macro :as handlers-macro]
|
||||
[status-im.data-store.messages :as messages-store]))
|
||||
|
||||
(defn multi-user-chat? [chat-id cofx]
|
||||
(get-in cofx [:db :chats chat-id :group-chat]))
|
||||
|
||||
(defn group-chat? [chat-id cofx]
|
||||
(and (multi-user-chat? chat-id cofx)
|
||||
(not (get-in cofx [:db :chats chat-id :public?]))))
|
||||
|
||||
(defn set-chat-ui-props
|
||||
"Updates ui-props in active chat by merging provided kvs into them"
|
||||
[{:keys [current-chat-id] :as db} kvs]
|
||||
|
@ -60,21 +72,50 @@
|
|||
:group-admin admin
|
||||
:contacts participants} cofx))
|
||||
|
||||
(defn new-update? [{:keys [added-to-at removed-at removed-from-at]} timestamp]
|
||||
(and (> timestamp added-to-at)
|
||||
(> timestamp removed-at)
|
||||
(> timestamp removed-from-at)))
|
||||
(defn clear-history [chat-id {:keys [db] :as cofx}]
|
||||
(let [{:keys [messages
|
||||
deleted-at-clock-value]} (get-in db [:chats chat-id])
|
||||
last-message-clock-value (or (->> messages
|
||||
vals
|
||||
(sort-by (comp unchecked-negate :clock-value))
|
||||
first
|
||||
:clock-value)
|
||||
deleted-at-clock-value
|
||||
(utils.clocks/send 0))]
|
||||
;; Necessary until we adjust merge-fx to cater for :txs
|
||||
(-> (select-keys cofx [:data-store/tx :db])
|
||||
(assoc-in [:db :chats chat-id :messages] {})
|
||||
(assoc-in [:db :chats chat-id :message-groups] {})
|
||||
(assoc-in [:db :chats chat-id :deleted-at-clock-value] last-message-clock-value)
|
||||
(update :data-store/tx concat [(chats-store/clear-history-tx chat-id last-message-clock-value)
|
||||
(messages-store/delete-messages-tx chat-id)]))))
|
||||
|
||||
(defn- remove-transport [chat-id {:keys [db] :as cofx}]
|
||||
;; if this is private group chat, we have to broadcast leave and unsubscribe after that
|
||||
(if (group-chat? chat-id cofx)
|
||||
(transport.message/send (transport.group-chat/GroupLeave.) chat-id cofx)
|
||||
(transport.utils/unsubscribe-from-chat chat-id cofx)))
|
||||
|
||||
(defn- deactivate-chat [chat-id {:keys [db now] :as cofx}]
|
||||
(assoc-in {:db db
|
||||
:data-store/tx [(chats-store/deactivate-chat-tx chat-id now)]}
|
||||
[:db :chats chat-id :is-active] false))
|
||||
|
||||
;; TODO: There's a race condition here, as the removal of the filter (async)
|
||||
;; is done at the same time as the removal of the chat, so a message
|
||||
;; might come between and restore the chat. Multiple way to handle this
|
||||
;; (remove chat only after the filter has been removed, probably the safest,
|
||||
;; flag the chat to ignore new messages, change receive method for public/group chats)
|
||||
;; For now to keep the code simplier and avoid significant changes, best to leave as it is.
|
||||
(defn remove-chat [chat-id {:keys [db now] :as cofx}]
|
||||
(let [{:keys [chat-id group-chat debug?]} (get-in db [:chats chat-id])]
|
||||
(if debug?
|
||||
(-> {:db db}
|
||||
(update-in [:db :chats] dissoc chat-id)
|
||||
(assoc :data-store/tx [(chats-store/delete-chat-tx chat-id)
|
||||
(messages-store/delete-messages-tx chat-id)]))
|
||||
(-> {:db db}
|
||||
(assoc-in [:db :chats chat-id :is-active] false)
|
||||
(assoc :data-store/tx [(chats-store/deactivate-chat-tx chat-id now)])))))
|
||||
(letfn [(remove-transport-fx [chat-id cofx]
|
||||
(when (multi-user-chat? chat-id cofx)
|
||||
(remove-transport chat-id cofx)))]
|
||||
(handlers-macro/merge-fx
|
||||
cofx
|
||||
(remove-transport-fx chat-id)
|
||||
(deactivate-chat chat-id)
|
||||
(clear-history chat-id))))
|
||||
|
||||
(defn bot-only-chat? [db chat-id]
|
||||
(let [{:keys [group-chat contacts]} (get-in db [:chats chat-id])]
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
(ns status-im.chat.models.group-chat
|
||||
(:require
|
||||
[clojure.set :as set]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.ui.screens.group.core :as group]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.transport.message.v1.group-chat :as transport.group-chat]
|
||||
[status-im.utils.handlers-macro :as handlers-macro]
|
||||
[status-im.chat.models.message :as models.message]))
|
||||
|
||||
(defn- participants-diff [existing-participants-set new-participants-set]
|
||||
{:removed (set/difference existing-participants-set new-participants-set)
|
||||
:added (set/difference new-participants-set existing-participants-set)})
|
||||
|
||||
(defn- prepare-system-message [admin-name added-participants removed-participants contacts]
|
||||
(let [added-participants-names (map #(get-in contacts [% :name] %) added-participants)
|
||||
removed-participants-names (map #(get-in contacts [% :name] %) removed-participants)]
|
||||
(cond
|
||||
(and (seq added-participants) (seq removed-participants))
|
||||
(str admin-name " "
|
||||
(i18n/label :t/invited) " " (apply str (interpose ", " added-participants-names))
|
||||
" and "
|
||||
(i18n/label :t/removed) " " (apply str (interpose ", " removed-participants-names)))
|
||||
|
||||
(seq added-participants)
|
||||
(str admin-name " " (i18n/label :t/invited) " " (apply str (interpose ", " added-participants-names)))
|
||||
|
||||
(seq removed-participants)
|
||||
(str admin-name " " (i18n/label :t/removed) " " (apply str (interpose ", " removed-participants-names))))))
|
||||
|
||||
(defn handle-group-admin-update [{:keys [chat-name participants]} chat-id signature {:keys [now db random-id] :as cofx}]
|
||||
(let [me (:current-public-key db)]
|
||||
;; we have to check if we already have a chat, or it's a new one
|
||||
(if-let [{:keys [group-admin contacts] :as chat} (get-in db [:chats chat-id])]
|
||||
;; update for existing group chat
|
||||
(when (and (= signature group-admin) ;; make sure that admin is the one making changes
|
||||
(not= (set contacts) (set participants))) ;; make sure it's actually changing something
|
||||
(let [{:keys [removed added]} (participants-diff (set contacts) (set participants))
|
||||
admin-name (or (get-in db [:contacts/contacts group-admin :name])
|
||||
group-admin)]
|
||||
(if (removed me) ;; we were removed
|
||||
(handlers-macro/merge-fx cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id random-id now
|
||||
(str admin-name " " (i18n/label :t/removed-from-chat))))
|
||||
(models.chat/upsert-chat {:chat-id chat-id
|
||||
:removed-from-at now
|
||||
:is-active false})
|
||||
(transport.utils/unsubscribe-from-chat chat-id))
|
||||
(handlers-macro/merge-fx cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id random-id now
|
||||
(prepare-system-message admin-name
|
||||
added
|
||||
removed
|
||||
(:contacts/contacts db))))
|
||||
(group/participants-added chat-id added)
|
||||
(group/participants-removed chat-id removed)))))
|
||||
;; first time we hear about chat -> create it if we are among participants
|
||||
(when (get (set participants) me)
|
||||
(models.chat/add-group-chat chat-id chat-name signature participants cofx)))))
|
||||
|
||||
(defn handle-group-leave [chat-id signature {:keys [db random-id now] :as cofx}]
|
||||
(let [me (:current-public-key db)
|
||||
participant-leaving-name (or (get-in db [:contacts/contacts signature :name])
|
||||
signature)]
|
||||
(when (and
|
||||
(not= signature me)
|
||||
(get-in db [:chats chat-id])) ;; chat is present
|
||||
(handlers-macro/merge-fx cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id random-id now
|
||||
(str participant-leaving-name " " (i18n/label :t/left))))
|
||||
(group/participants-removed chat-id #{signature})
|
||||
(transport.group-chat/send-new-group-key nil chat-id)))))
|
|
@ -106,10 +106,6 @@
|
|||
(def ^:private- add-single-message (partial add-message false))
|
||||
(def ^:private- add-batch-message (partial add-message true))
|
||||
|
||||
(defn- prepare-chat [chat-id {:keys [db now] :as cofx}]
|
||||
(chat-model/upsert-chat {:chat-id chat-id
|
||||
:timestamp now} cofx))
|
||||
|
||||
(defn- send-message-seen [chat-id message-id send-seen? cofx]
|
||||
(when send-seen?
|
||||
(transport/send (protocol/map->MessagesSeen {:message-ids #{message-id}}) chat-id cofx)))
|
||||
|
@ -198,12 +194,18 @@
|
|||
(#{:group-user-message :public-group-user-message} message-type))
|
||||
|
||||
(defn add-to-chat?
|
||||
[{:keys [db]} {:keys [chat-id from message-id] :as message}]
|
||||
[{:keys [db]} {:keys [chat-id
|
||||
clock-value
|
||||
from
|
||||
message-id] :as message}]
|
||||
(let [{:keys [chats current-public-key]} db
|
||||
{:keys [messages not-loaded-message-ids]} (get chats chat-id)]
|
||||
{:keys [deleted-at-clock-value
|
||||
messages
|
||||
not-loaded-message-ids]} (get chats chat-id)]
|
||||
(when (not= from current-public-key)
|
||||
(not (or (get messages message-id)
|
||||
(get not-loaded-message-ids message-id))))))
|
||||
(get not-loaded-message-ids message-id)
|
||||
(>= deleted-at-clock-value clock-value))))))
|
||||
|
||||
(defn message-seen-by? [message user-pk]
|
||||
(= :seen (get-in message [:user-statuses user-pk])))
|
||||
|
|
|
@ -18,24 +18,19 @@
|
|||
{:label (i18n/label :t/delete-chat)
|
||||
:action #(re-frame/dispatch [:remove-chat-and-navigate-home? chat-id group?])})
|
||||
|
||||
(defn- leave-group-chat [chat-id public?]
|
||||
{:label (i18n/label (if public? :t/leave-public-chat :t/leave-group-chat))
|
||||
:action #(re-frame/dispatch [:leave-group-chat? chat-id])})
|
||||
|
||||
(defn- chat-actions [chat-id]
|
||||
[(view-profile chat-id)
|
||||
(clear-history)
|
||||
(delete-chat chat-id false)])
|
||||
|
||||
(defn- group-chat-actions [chat-id]
|
||||
[(group-info chat-id)
|
||||
(clear-history)
|
||||
(delete-chat chat-id true)
|
||||
(leave-group-chat chat-id false)])
|
||||
(delete-chat chat-id true)])
|
||||
|
||||
(defn- public-chat-actions [chat-id]
|
||||
[(clear-history)
|
||||
(delete-chat chat-id true)
|
||||
(leave-group-chat chat-id true)])
|
||||
(delete-chat chat-id true)])
|
||||
|
||||
(defn actions [group-chat? chat-id public?]
|
||||
(cond
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
[status-im.ui.components.react :as react]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.native-module.core :as status]
|
||||
status-im.transport.impl.receive
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.react-native.js-dependencies :as js-dependencies]
|
||||
|
|
|
@ -37,6 +37,14 @@
|
|||
(defn- get-chat-by-id [chat-id realm]
|
||||
(core/single (core/get-by-field realm :chat :chat-id chat-id)))
|
||||
|
||||
(defn clear-history-tx
|
||||
"Returns tx function for clearing the history of chat"
|
||||
[chat-id deleted-at-clock-value]
|
||||
(fn [realm]
|
||||
(let [chat (get-chat-by-id chat-id realm)]
|
||||
(doto chat
|
||||
(aset "deleted-at-clock-value" deleted-at-clock-value)))))
|
||||
|
||||
(defn deactivate-chat-tx
|
||||
"Returns tx function for deactivating chat"
|
||||
[chat-id now]
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
(ns status-im.data-store.realm.schemas.account.core
|
||||
(:require [status-im.data-store.realm.schemas.account.v1.core :as v1]
|
||||
[status-im.data-store.realm.schemas.account.v2.core :as v2]))
|
||||
(:require
|
||||
[status-im.data-store.realm.schemas.account.v1.core :as v1]
|
||||
[status-im.data-store.realm.schemas.account.v2.core :as v2]
|
||||
[status-im.data-store.realm.schemas.account.v3.core :as v3]))
|
||||
|
||||
;; TODO(oskarth): Add failing test if directory vXX exists but isn't in schemas.
|
||||
|
||||
|
@ -10,5 +12,7 @@
|
|||
:migration v1/migration}
|
||||
{:schema v2/schema
|
||||
:schemaVersion 2
|
||||
:migration v2/migration}])
|
||||
|
||||
:migration v2/migration}
|
||||
{:schema v3/schema
|
||||
:schemaVersion 3
|
||||
:migration v3/migration}])
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
(ns status-im.data-store.realm.schemas.account.v3.chat
|
||||
(:require [status-im.ui.components.styles :refer [default-chat-color]]))
|
||||
|
||||
(def schema {:name :chat
|
||||
:primaryKey :chat-id
|
||||
:properties {:chat-id :string
|
||||
:name :string
|
||||
:color {:type :string
|
||||
:default default-chat-color}
|
||||
:group-chat {:type :bool
|
||||
:indexed true}
|
||||
:group-admin {:type :string
|
||||
:optional true}
|
||||
:is-active :bool
|
||||
:timestamp :int
|
||||
:contacts {:type "string[]"}
|
||||
:removed-at {:type :int
|
||||
:optional true}
|
||||
:removed-from-at {:type :int
|
||||
:optional true}
|
||||
:deleted-at-clock-value {:type :int
|
||||
:optional true}
|
||||
:added-to-at {:type :int
|
||||
:optional true}
|
||||
:updated-at {:type :int
|
||||
:optional true}
|
||||
:message-overhead {:type :int
|
||||
:default 0}
|
||||
:contact-info {:type :string
|
||||
:optional true}
|
||||
:debug? {:type :bool
|
||||
:default false}
|
||||
:public? {:type :bool
|
||||
:default false}}})
|
|
@ -0,0 +1,27 @@
|
|||
(ns status-im.data-store.realm.schemas.account.v3.core
|
||||
(:require [status-im.data-store.realm.schemas.account.v3.chat :as chat]
|
||||
[status-im.data-store.realm.schemas.account.v1.transport :as transport]
|
||||
[status-im.data-store.realm.schemas.account.v1.contact :as contact]
|
||||
[status-im.data-store.realm.schemas.account.v1.message :as message]
|
||||
[status-im.data-store.realm.schemas.account.v1.request :as request]
|
||||
[status-im.data-store.realm.schemas.account.v2.mailserver :as mailserver]
|
||||
[status-im.data-store.realm.schemas.account.v1.user-status :as user-status]
|
||||
[status-im.data-store.realm.schemas.account.v1.local-storage :as local-storage]
|
||||
[status-im.data-store.realm.schemas.account.v1.browser :as browser]
|
||||
[goog.object :as object]
|
||||
[taoensso.timbre :as log]
|
||||
[cljs.reader :as reader]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(def schema [chat/schema
|
||||
transport/schema
|
||||
contact/schema
|
||||
message/schema
|
||||
request/schema
|
||||
mailserver/schema
|
||||
user-status/schema
|
||||
local-storage/schema
|
||||
browser/schema])
|
||||
|
||||
(defn migration [old-realm new-realm]
|
||||
(log/debug "migrating v3 account database: " old-realm new-realm))
|
|
@ -296,7 +296,7 @@
|
|||
:clear "Clear"
|
||||
:clear-history "Clear history"
|
||||
:clear-history-title "Clear history?"
|
||||
:clear-group-history-confirmation "Are you sure you want to clear this group's chat history?"
|
||||
:clear-history-confirmation-content "Are you sure you want to clear this chat history?"
|
||||
:clear-history-confirmation "Clear history?"
|
||||
:clear-history-action "Clear"
|
||||
:mute-notifications "Mute notifications"
|
||||
|
|
|
@ -63,8 +63,6 @@
|
|||
(fn [js-error js-message]
|
||||
(re-frame/dispatch [:protocol/receive-whisper-message js-error js-message chat-id])))))
|
||||
|
||||
(def unsubscribe-from-chat transport.utils/unsubscribe-from-chat)
|
||||
|
||||
(defn stop-whisper
|
||||
"Stops whisper protocol by removing all existing shh filters
|
||||
It is necessary to remove the filters because status-go there isn't currently a logout feature in status-go
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
:group/unsubscribe-from-chat
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [chat-id]]
|
||||
(transport/unsubscribe-from-chat chat-id cofx)))
|
||||
(transport.utils/unsubscribe-from-chat chat-id cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:group/send-new-sym-key
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
(ns status-im.transport.impl.receive
|
||||
(:require
|
||||
[status-im.chat.models.group-chat :as models.group-chat]
|
||||
[status-im.ui.screens.contacts.core :as contacts]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.transport.message.v1.contact :as transport.contact]
|
||||
[status-im.transport.message.v1.group-chat :as transport.group-chat]))
|
||||
|
||||
(extend-type transport.group-chat/GroupAdminUpdate
|
||||
message/StatusMessage
|
||||
(receive [this chat-id signature cofx]
|
||||
(models.group-chat/handle-group-admin-update this chat-id signature cofx)))
|
||||
|
||||
(extend-type transport.group-chat/GroupLeave
|
||||
message/StatusMessage
|
||||
(receive [this chat-id signature cofx]
|
||||
(models.group-chat/handle-group-leave chat-id signature cofx)))
|
||||
|
||||
(extend-type transport.contact/ContactRequest
|
||||
message/StatusMessage
|
||||
(receive [this chat-id signature cofx]
|
||||
(contacts/receive-contact-request signature this cofx)))
|
||||
|
||||
(extend-type transport.contact/ContactRequestConfirmed
|
||||
message/StatusMessage
|
||||
(receive [this chat-id signature cofx]
|
||||
(contacts/receive-contact-request-confirmation signature this cofx)))
|
||||
|
||||
(extend-type transport.contact/ContactUpdate
|
||||
message/StatusMessage
|
||||
(receive [this chat-id signature cofx]
|
||||
(contacts/receive-contact-update chat-id
|
||||
signature
|
||||
this cofx)))
|
|
@ -4,7 +4,6 @@
|
|||
[status-im.transport.message.core :as message]
|
||||
[status-im.transport.message.v1.protocol :as protocol]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.ui.screens.contacts.core :as contacts]
|
||||
[status-im.utils.handlers-macro :as handlers-macro]))
|
||||
|
||||
(defrecord NewContactKey [sym-key topic message]
|
||||
|
@ -41,20 +40,14 @@
|
|||
(handlers-macro/merge-fx cofx
|
||||
{:shh/get-new-sym-keys [{:web3 (:web3 db)
|
||||
:on-success on-success}]}
|
||||
(protocol/init-chat chat-id topic))))
|
||||
(receive [this chat-id signature {:keys [db] :as cofx}]
|
||||
(contacts/receive-contact-request signature this cofx)))
|
||||
(protocol/init-chat chat-id topic)))))
|
||||
|
||||
(defrecord ContactRequestConfirmed [name profile-image address fcm-token]
|
||||
message/StatusMessage
|
||||
(send [this chat-id cofx]
|
||||
(handlers-macro/merge-fx cofx
|
||||
(protocol/send {:chat-id chat-id
|
||||
:payload this})))
|
||||
(receive [this chat-id signature cofx]
|
||||
(handlers-macro/merge-fx cofx
|
||||
(contacts/receive-contact-request-confirmation signature
|
||||
this))))
|
||||
:payload this}))))
|
||||
|
||||
(defrecord ContactUpdate [name profile-image]
|
||||
message/StatusMessage
|
||||
|
@ -62,9 +55,4 @@
|
|||
(let [public-keys (remove nil? (map :public-key (vals (:contacts/contacts db))))]
|
||||
(handlers-macro/merge-fx cofx
|
||||
(protocol/multi-send-by-pubkey {:public-keys public-keys
|
||||
:payload this}))))
|
||||
(receive [this chat-id signature cofx]
|
||||
(handlers-macro/merge-fx cofx
|
||||
(contacts/receive-contact-update chat-id
|
||||
signature
|
||||
this))))
|
||||
:payload this})))))
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
(ns ^{:doc "Group chat API"}
|
||||
status-im.transport.message.v1.group-chat
|
||||
(:require [clojure.set :as set]
|
||||
[re-frame.core :as re-frame]
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.utils.handlers-macro :as handlers-macro]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.screens.group.core :as group]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.transport.message.v1.protocol :as protocol]
|
||||
[status-im.transport.utils :as transport.utils]))
|
||||
|
||||
|
@ -44,7 +39,7 @@
|
|||
(= (get-in cofx [:db :chats chat-id :group-admin])
|
||||
(get-in cofx [:db :current-public-key])))
|
||||
|
||||
(defn- send-new-group-key [message chat-id cofx]
|
||||
(defn send-new-group-key [message chat-id cofx]
|
||||
(when (user-is-group-admin? chat-id cofx)
|
||||
{:shh/get-new-sym-keys [{:web3 (get-in cofx [:db :web3])
|
||||
:on-success (fn [sym-key sym-key-id]
|
||||
|
@ -55,83 +50,21 @@
|
|||
:sym-key-id sym-key-id
|
||||
:message message}]))}]}))
|
||||
|
||||
(defn- prepare-system-message [admin-name added-participants removed-participants contacts]
|
||||
(let [added-participants-names (map #(get-in contacts [% :name] %) added-participants)
|
||||
removed-participants-names (map #(get-in contacts [% :name] %) removed-participants)]
|
||||
(cond
|
||||
(and (seq added-participants) (seq removed-participants))
|
||||
(str admin-name " "
|
||||
(i18n/label :t/invited) " " (apply str (interpose ", " added-participants-names))
|
||||
" and "
|
||||
(i18n/label :t/removed) " " (apply str (interpose ", " removed-participants-names)))
|
||||
|
||||
(seq added-participants)
|
||||
(str admin-name " " (i18n/label :t/invited) " " (apply str (interpose ", " added-participants-names)))
|
||||
|
||||
(seq removed-participants)
|
||||
(str admin-name " " (i18n/label :t/removed) " " (apply str (interpose ", " removed-participants-names))))))
|
||||
|
||||
(defn- init-chat-if-new [chat-id cofx]
|
||||
(if (nil? (get-in cofx [:db :transport/chats chat-id]))
|
||||
(protocol/init-chat chat-id cofx)))
|
||||
|
||||
(defn- participants-diff [existing-participants-set new-participants-set]
|
||||
{:removed (set/difference existing-participants-set new-participants-set)
|
||||
:added (set/difference new-participants-set existing-participants-set)})
|
||||
|
||||
(defrecord GroupAdminUpdate [chat-name participants]
|
||||
message/StatusMessage
|
||||
(send [this chat-id cofx]
|
||||
(handlers-macro/merge-fx cofx
|
||||
(init-chat-if-new chat-id)
|
||||
(send-new-group-key this chat-id)))
|
||||
(receive [this chat-id signature {:keys [now db random-id] :as cofx}]
|
||||
(let [me (:current-public-key db)]
|
||||
;; we have to check if we already have a chat, or it's a new one
|
||||
(if-let [{:keys [group-admin contacts] :as chat} (get-in db [:chats chat-id])]
|
||||
;; update for existing group chat
|
||||
(when (and (= signature group-admin) ;; make sure that admin is the one making changes
|
||||
(not= (set contacts) (set participants))) ;; make sure it's actually changing something
|
||||
(let [{:keys [removed added]} (participants-diff (set contacts) (set participants))
|
||||
admin-name (or (get-in db [:contacts/contacts group-admin :name])
|
||||
group-admin)]
|
||||
(if (removed me) ;; we were removed
|
||||
(handlers-macro/merge-fx cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id random-id now
|
||||
(str admin-name " " (i18n/label :t/removed-from-chat))))
|
||||
(models.chat/upsert-chat {:chat-id chat-id
|
||||
:removed-from-at now
|
||||
:is-active false})
|
||||
(transport.utils/unsubscribe-from-chat chat-id))
|
||||
(handlers-macro/merge-fx cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id random-id now
|
||||
(prepare-system-message admin-name
|
||||
added
|
||||
removed
|
||||
(:contacts/contacts db))))
|
||||
(group/participants-added chat-id added)
|
||||
(group/participants-removed chat-id removed)))))
|
||||
;; first time we hear about chat -> create it if we are among participants
|
||||
(when (get (set participants) me)
|
||||
(models.chat/add-group-chat chat-id chat-name signature participants cofx))))))
|
||||
(send-new-group-key this chat-id))))
|
||||
|
||||
(defrecord GroupLeave []
|
||||
message/StatusMessage
|
||||
(send [this chat-id cofx]
|
||||
(protocol/send {:chat-id chat-id
|
||||
:payload this
|
||||
:success-event [::unsubscribe-from-chat chat-id]}
|
||||
cofx))
|
||||
(receive [this chat-id signature {:keys [db now random-id] :as cofx}]
|
||||
(let [me (:current-public-key db)
|
||||
participant-leaving-name (or (get-in db [:contacts/contacts signature :name])
|
||||
signature)]
|
||||
(when (get-in db [:chats chat-id]) ;; chat is present
|
||||
(handlers-macro/merge-fx cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id random-id now
|
||||
(str participant-leaving-name " " (i18n/label :t/left))))
|
||||
(group/participants-removed chat-id #{signature})
|
||||
(send-new-group-key nil chat-id))))))
|
||||
:success-event [:group/unsubscribe-from-chat chat-id]}
|
||||
cofx)))
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im.ui.screens.navigation :as navigation]
|
||||
[status-im.transport.message.v1.group-chat :as group-chat]
|
||||
[status-im.transport.message.core :as transport]
|
||||
|
@ -57,19 +58,3 @@
|
|||
{:db (assoc-in db [:chats current-chat-id :name] new-chat-name)
|
||||
:data-store/tx [(chats-store/save-chat-tx {:chat-id current-chat-id
|
||||
:name new-chat-name})]}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:clear-history
|
||||
(fn [{{:keys [current-chat-id] :as db} :db} _]
|
||||
{:db (-> db
|
||||
(assoc-in [:chats current-chat-id :messages] {})
|
||||
(assoc-in [:chats current-chat-id :message-groups] {}))
|
||||
:data-store/tx [(messages-store/hide-messages-tx current-chat-id)]}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:clear-history?
|
||||
(fn [_ _]
|
||||
{:show-confirmation {:title (i18n/label :t/clear-history-confirmation)
|
||||
:content (i18n/label :t/clear-group-history-confirmation)
|
||||
:confirm-button-text (i18n/label :t/clear)
|
||||
:on-accept #(re-frame/dispatch [:clear-history])}}))
|
||||
|
|
|
@ -44,25 +44,17 @@
|
|||
[{:label (i18n/label :t/clear-history)
|
||||
:icon :icons/close
|
||||
:action #(utils/show-confirmation (i18n/label :t/clear-history-title)
|
||||
(i18n/label :t/clear-group-history-confirmation)
|
||||
(i18n/label :t/clear-history-confirmation-content)
|
||||
(i18n/label :t/clear-history-action)
|
||||
(fn [] (re-frame/dispatch [:clear-history])))
|
||||
:accessibility-label :clear-history-button}
|
||||
{:label (i18n/label :t/delete-chat)
|
||||
:icon :icons/delete
|
||||
:action #(utils/show-confirmation (i18n/label :t/delete-chat-title)
|
||||
(i18n/label :t/delete-group-chat-confirmation)
|
||||
(i18n/label :t/delete)
|
||||
(fn []
|
||||
(re-frame/dispatch [:remove-chat-and-navigate-home chat-id])))
|
||||
:accessibility-label :delete-chat-button}
|
||||
{:label (i18n/label :t/leave-group)
|
||||
:icon :icons/arrow-left
|
||||
:action #(utils/show-confirmation (i18n/label :t/leave-group-title)
|
||||
(i18n/label :t/leave-group-confirmation)
|
||||
(i18n/label :t/leave-group-action)
|
||||
(fn [] (re-frame/dispatch [:remove-chat-and-navigate-home chat-id])))
|
||||
:accessibility-label :leave-chat-button}]))
|
||||
:accessibility-label :delete-chat-button}]))
|
||||
|
||||
(defn contact-actions [contact]
|
||||
[{:action #(re-frame/dispatch [:show-profile (:whisper-identity contact)])
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
(ns status-im.test.chat.models
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.utils.clocks :as utils.clocks]
|
||||
[status-im.chat.models :as chat]))
|
||||
|
||||
(deftest upsert-chat-test
|
||||
|
@ -100,3 +101,104 @@
|
|||
(is (:group-chat chat)))
|
||||
(testing "it does not sets the public flag"
|
||||
(is (:public? chat)))))
|
||||
|
||||
(deftest clear-history-test
|
||||
(let [chat-id "1"
|
||||
cofx {:db {:chats {chat-id {:message-groups {:something "a"}
|
||||
:messages {"1" {:clock-value 1}
|
||||
"2" {:clock-value 10}
|
||||
"3" {:clock-value 2}}}}}}]
|
||||
(testing "it deletes all the messages"
|
||||
(let [actual (chat/clear-history chat-id cofx)]
|
||||
(is (= {} (get-in actual [:db :chats chat-id :messages])))))
|
||||
(testing "it deletes all the message groups"
|
||||
(let [actual (chat/clear-history chat-id cofx)]
|
||||
(is (= {} (get-in actual [:db :chats chat-id :message-groups])))))
|
||||
(testing "it sets a deleted-at-clock-value equal to the last message clock-value"
|
||||
(let [actual (chat/clear-history chat-id cofx)]
|
||||
(is (= 10 (get-in actual [:db :chats chat-id :deleted-at-clock-value])))))
|
||||
(testing "it does not override the deleted-at-clock-value when there are no messages"
|
||||
(let [actual (chat/clear-history chat-id
|
||||
(update-in cofx
|
||||
[:db :chats chat-id]
|
||||
assoc
|
||||
:messages {}
|
||||
:deleted-at-clock-value 100))]
|
||||
(is (= 100 (get-in actual [:db :chats chat-id :deleted-at-clock-value])))))
|
||||
(testing "it set the deleted-at-clock-value to now the chat has no messages nor previous deleted-at"
|
||||
(with-redefs [utils.clocks/send (constantly 42)]
|
||||
(let [actual (chat/clear-history chat-id
|
||||
(update-in cofx
|
||||
[:db :chats chat-id]
|
||||
assoc
|
||||
:messages {}))]
|
||||
(is (= 42 (get-in actual [:db :chats chat-id :deleted-at-clock-value]))))))
|
||||
(testing "it adds the relevant transactions for realm"
|
||||
(let [actual (chat/clear-history chat-id cofx)]
|
||||
(is (:data-store/tx actual))
|
||||
(is (= 2 (count (:data-store/tx actual))))))))
|
||||
|
||||
(deftest remove-chat-test
|
||||
(let [chat-id "1"
|
||||
cofx {:db {:transport/chats {chat-id {}}
|
||||
:chats {chat-id {:messages {"1" {:clock-value 1}
|
||||
"2" {:clock-value 10}
|
||||
"3" {:clock-value 2}}}}}}]
|
||||
(testing "it deletes all the messages"
|
||||
(let [actual (chat/remove-chat chat-id cofx)]
|
||||
(is (= {} (get-in actual [:db :chats chat-id :messages])))))
|
||||
(testing "it sets a deleted-at-clock-value equal to the last message clock-value"
|
||||
(let [actual (chat/remove-chat chat-id cofx)]
|
||||
(is (= 10 (get-in actual [:db :chats chat-id :deleted-at-clock-value])))))
|
||||
(testing "it sets the chat as inactive"
|
||||
(let [actual (chat/remove-chat chat-id cofx)]
|
||||
(is (= false (get-in actual [:db :chats chat-id :is-active])))))
|
||||
(testing "it removes it from transport if it's a public chat"
|
||||
(let [actual (chat/remove-chat chat-id
|
||||
(update-in
|
||||
cofx
|
||||
[:db :chats chat-id]
|
||||
assoc
|
||||
:group-chat true
|
||||
:public? true))]
|
||||
(is (not (get-in actual [:db :transport/chats chat-id])))))
|
||||
(testing "it sends a leave group request if it's a group-chat"
|
||||
(let [actual (chat/remove-chat chat-id
|
||||
(assoc-in
|
||||
cofx
|
||||
[:db :chats chat-id :group-chat]
|
||||
true))]
|
||||
(is (:shh/post actual))
|
||||
(testing "it does not remove transport, only after send is successful"
|
||||
(is (get-in actual [:db :transport/chats chat-id])))))
|
||||
(testing "it does not remove it from transport if it's a one-to-one"
|
||||
(let [actual (chat/remove-chat chat-id cofx)]
|
||||
(is (get-in actual [:db :transport/chats chat-id]))))
|
||||
(testing "it adds the relevant transactions for realm"
|
||||
(let [actual (chat/remove-chat chat-id cofx)]
|
||||
(is (:data-store/tx actual))
|
||||
(is (= 3 (count (:data-store/tx actual))))))))
|
||||
|
||||
(deftest multi-user-chat?
|
||||
(let [chat-id "1"]
|
||||
(testing "it returns true if it's a group chat"
|
||||
(let [cofx {:db {:chats {chat-id {:group-chat true}}}}]
|
||||
(is (chat/multi-user-chat? chat-id cofx))))
|
||||
(testing "it returns true if it's a public chat"
|
||||
(let [cofx {:db {:chats {chat-id {:public? true :group-chat true}}}}]
|
||||
(is (chat/multi-user-chat? chat-id cofx))))
|
||||
(testing "it returns false if it's a 1-to-1 chat"
|
||||
(let [cofx {:db {:chats {chat-id {}}}}]
|
||||
(is (not (chat/multi-user-chat? chat-id cofx)))))))
|
||||
|
||||
(deftest group-chat?
|
||||
(let [chat-id "1"]
|
||||
(testing "it returns true if it's a group chat"
|
||||
(let [cofx {:db {:chats {chat-id {:group-chat true}}}}]
|
||||
(is (chat/group-chat? chat-id cofx))))
|
||||
(testing "it returns false if it's a public chat"
|
||||
(let [cofx {:db {:chats {chat-id {:public? true :group-chat true}}}}]
|
||||
(is (not (chat/group-chat? chat-id cofx)))))
|
||||
(testing "it returns false if it's a 1-to-1 chat"
|
||||
(let [cofx {:db {:chats {chat-id {}}}}]
|
||||
(is (not (chat/group-chat? chat-id cofx)))))))
|
||||
|
|
|
@ -9,22 +9,44 @@
|
|||
(is (message/add-to-chat? {:db {:chats {"a" {}}}}
|
||||
{:message-id "message-id"
|
||||
:from "a"
|
||||
:clock-value 1
|
||||
:chat-id "a"})))
|
||||
(testing "it returns false when from is the same as pk"
|
||||
(is (not (message/add-to-chat? {:db {:current-public-key "pk"
|
||||
:chats {"a" {}}}}
|
||||
{:message-id "message-id"
|
||||
:from "pk"
|
||||
:clock-value 1
|
||||
:chat-id "a"}))))
|
||||
(testing "it returns false when it's already in the loaded message"
|
||||
(is (not (message/add-to-chat? {:db {:chats {"a" {:messages {"message-id" {}}}}}}
|
||||
{:message-id "message-id"
|
||||
:from "a"
|
||||
:clock-value 1
|
||||
:chat-id "a"}))))
|
||||
(testing "it returns false when it's already in the not-loaded-message-ids"
|
||||
(is (not (message/add-to-chat? {:db {:chats {"a" {:not-loaded-message-ids #{"message-id"}}}}}
|
||||
{:message-id "message-id"
|
||||
:from "a"
|
||||
:clock-value 1
|
||||
:chat-id "a"}))))
|
||||
(testing "it returns false when the clock-value is the same as the deleted-clock-value in chat"
|
||||
(is (not (message/add-to-chat? {:db {:chats {"a" {:deleted-at-clock-value 1}}}}
|
||||
{:message-id "message-id"
|
||||
:from "a"
|
||||
:clock-value 1
|
||||
:chat-id "a"}))))
|
||||
(testing "it returns true when the clock-value is greater than the deleted-clock-value in chat"
|
||||
(is (message/add-to-chat? {:db {:chats {"a" {:deleted-at-clock-value 1}}}}
|
||||
{:message-id "message-id"
|
||||
:from "a"
|
||||
:clock-value 2
|
||||
:chat-id "a"})))
|
||||
(testing "it returns false when the clock-value is less than the deleted-clock-value in chat"
|
||||
(is (not (message/add-to-chat? {:db {:chats {"a" {:deleted-at-clock-value 1}}}}
|
||||
{:message-id "message-id"
|
||||
:from "a"
|
||||
:clock-value 0
|
||||
:chat-id "a"})))))
|
||||
|
||||
(deftest receive-send-seen
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
[status-im.test.protocol.web3.inbox]
|
||||
[status-im.test.utils.utils]
|
||||
[status-im.test.utils.money]
|
||||
[status-im.test.utils.handlers-macro]
|
||||
[status-im.test.utils.clocks]
|
||||
[status-im.test.utils.inbox]
|
||||
[status-im.test.utils.ethereum.eip681]
|
||||
|
@ -66,6 +67,7 @@
|
|||
'status-im.test.transport.inbox
|
||||
'status-im.test.protocol.web3.inbox
|
||||
'status-im.test.utils.utils
|
||||
'status-im.test.utils.handlers-macro
|
||||
'status-im.test.utils.money
|
||||
'status-im.test.utils.clocks
|
||||
'status-im.test.utils.inbox
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
(ns status-im.test.utils.handlers-macro
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.utils.handlers-macro :as m]))
|
||||
|
||||
(deftest merge-fx
|
||||
(letfn [(add-b [cofx]
|
||||
(assoc-in cofx [:db :b] "b"))
|
||||
(add-c [cofx]
|
||||
(assoc-in cofx [:db :c] "c"))
|
||||
(add-tx [tx cofx]
|
||||
(assoc cofx :data-store/tx [tx]))]
|
||||
(testing "it updates db correctly"
|
||||
(let [actual (m/merge-fx {:db {:a "a"}}
|
||||
(add-b)
|
||||
(add-c))]
|
||||
(is (= {:db {:a "a"
|
||||
:b "b"
|
||||
:c "c"}} actual))))
|
||||
(testing "it updates db correctly when a fn don't update it"
|
||||
(let [empty-fn (constantly nil)
|
||||
actual (m/merge-fx {:db {:a "a"}}
|
||||
(add-b)
|
||||
(empty-fn)
|
||||
(add-c))]
|
||||
(is (= {:db {:a "a"
|
||||
:b "b"
|
||||
:c "c"}} actual))))
|
||||
#_(testing "it updates data-store/tx correctly"
|
||||
(let [actual (m/merge-fx {:data-store/tx [1]}
|
||||
(add-tx 2)
|
||||
(add-tx 3))]
|
||||
(is (= {:data-store/tx [1 2 3]} actual))))))
|
Loading…
Reference in New Issue