mirror of
https://github.com/status-im/status-react.git
synced 2025-01-12 03:54:32 +00:00
move chat events (#14835)
This commit is contained in:
parent
b8dfa6b645
commit
f0272f2e77
@ -2,7 +2,7 @@
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.add-new.db :as db]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im2.contexts.chat.events :as chat]
|
||||
[status-im.contact.core :as contact]
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
[status-im.ethereum.ens :as ens]
|
||||
|
@ -1,95 +0,0 @@
|
||||
(ns status-im.chat.db
|
||||
(:require [status-im2.constants :as constants]))
|
||||
|
||||
(defn group-chat-name
|
||||
[{:keys [public? name]}]
|
||||
(str (when public? "#") name))
|
||||
|
||||
(defn intersperse-datemark
|
||||
"Reduce step which expects the input list of messages to be sorted by clock value.
|
||||
It makes best effort to group them by day.
|
||||
We cannot sort them by :timestamp, as that represents the clock of the sender
|
||||
and we have no guarantees on the order.
|
||||
We naively and arbitrarly group them assuming that out-of-order timestamps
|
||||
fall in the previous bucket.
|
||||
A sends M1 to B with timestamp 2000-01-01T00:00:00
|
||||
B replies M2 with timestamp 1999-12-31-23:59:59
|
||||
M1 needs to be displayed before M2
|
||||
so we bucket both in 1999-12-31"
|
||||
[{:keys [acc last-timestamp last-datemark]} {:keys [whisper-timestamp datemark] :as msg}]
|
||||
(cond
|
||||
(empty? acc) ; initial element
|
||||
{:last-timestamp whisper-timestamp
|
||||
:last-datemark datemark
|
||||
:acc (conj acc msg)}
|
||||
|
||||
(and (not= last-datemark datemark) ; not the same day
|
||||
(< whisper-timestamp last-timestamp)) ; not out-of-order
|
||||
{:last-timestamp whisper-timestamp
|
||||
:last-datemark datemark
|
||||
:acc (conj acc
|
||||
{:value last-datemark ; intersperse datemark message
|
||||
:type :datemark}
|
||||
msg)}
|
||||
:else
|
||||
{:last-timestamp (min whisper-timestamp last-timestamp) ; use last datemark
|
||||
:last-datemark last-datemark
|
||||
:acc (conj acc (assoc msg :datemark last-datemark))}))
|
||||
|
||||
(defn add-datemarks
|
||||
"Add a datemark in between an ordered seq of messages when two datemarks are not
|
||||
the same. Ignore messages with out-of-order timestamps"
|
||||
[messages]
|
||||
(when (seq messages)
|
||||
(let [messages-with-datemarks (:acc (reduce intersperse-datemark {:acc []} messages))]
|
||||
; Append last datemark
|
||||
(conj messages-with-datemarks
|
||||
{:value (:datemark (peek messages-with-datemarks))
|
||||
:type :datemark}))))
|
||||
|
||||
(defn last-gap
|
||||
"last-gap is a special gap that is put last in the message stream"
|
||||
[chat-id synced-from]
|
||||
{:message-id "0x123"
|
||||
:message-type constants/message-type-gap
|
||||
:chat-id chat-id
|
||||
:content-type constants/content-type-gap
|
||||
:gap-ids #{:first-gap}
|
||||
:gap-parameters {:from synced-from}})
|
||||
|
||||
(defn collapse-gaps
|
||||
"collapse-gaps will take an array of messages and collapse any gap next to
|
||||
each other in a single gap.
|
||||
It will also append one last gap if the last message is a non-gap"
|
||||
[messages chat-id synced-from now chat-type joined loading-messages?]
|
||||
(let [messages-with-gaps (reduce
|
||||
(fn [acc {:keys [gap-parameters message-id] :as message}]
|
||||
(let [last-element (peek acc)]
|
||||
(cond
|
||||
;; If it's a message, just add
|
||||
(empty? gap-parameters)
|
||||
(conj acc message)
|
||||
|
||||
;; Both are gaps, merge them
|
||||
(and
|
||||
(seq (:gap-parameters last-element))
|
||||
(seq gap-parameters))
|
||||
(conj (pop acc) (update last-element :gap-ids conj message-id))
|
||||
|
||||
;; it's a gap
|
||||
:else
|
||||
(conj acc (assoc message :gap-ids #{message-id})))))
|
||||
[]
|
||||
messages)]
|
||||
(if (or loading-messages? ; it's loading messages from the database
|
||||
(nil? synced-from) ; it's still syncing
|
||||
(= constants/timeline-chat-type chat-type) ; it's a timeline chat
|
||||
(= constants/profile-chat-type chat-type) ; it's a profile chat
|
||||
(and (not (nil? synced-from)) ; it's not more than a month
|
||||
(<= synced-from (- (quot now 1000) constants/one-month)))
|
||||
(and (= constants/private-group-chat-type chat-type) ; it's a private group chat
|
||||
(or (not (pos? joined)) ; we haven't joined
|
||||
(>= (quot joined 1000) synced-from))) ; the history goes before we joined
|
||||
(:gap-ids (peek messages-with-gaps))) ; there's already a gap on top of the chat history
|
||||
messages-with-gaps ; don't add an extra gap
|
||||
(conj messages-with-gaps (last-gap chat-id synced-from)))))
|
@ -1,44 +0,0 @@
|
||||
(ns status-im.chat.db-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.chat.db :as db]))
|
||||
|
||||
(deftest group-chat-name
|
||||
(testing "it prepends # if it's a public chat"
|
||||
(is (= "#withhash"
|
||||
(db/group-chat-name {:group-chat true
|
||||
:chat-id "1"
|
||||
:public? true
|
||||
:name "withhash"}))))
|
||||
(testing "it leaves the name unchanged if it's a group chat"
|
||||
(is (= "unchanged"
|
||||
(db/group-chat-name {:group-chat true
|
||||
:chat-id "1"
|
||||
:name "unchanged"})))))
|
||||
|
||||
(deftest intersperse-datemarks
|
||||
(testing "it mantains the order even when timestamps are across days"
|
||||
(let [message-1 {:datemark "Dec 31, 1999"
|
||||
:whisper-timestamp 946641600000} ; 1999}
|
||||
message-2 {:datemark "Jan 1, 2000"
|
||||
:whisper-timestamp 946728000000} ; 2000 this will displayed in 1999
|
||||
message-3 {:datemark "Dec 31, 1999"
|
||||
:whisper-timestamp 946641600000} ; 1999
|
||||
message-4 {:datemark "Jan 1, 2000"
|
||||
:whisper-timestamp 946728000000} ; 2000
|
||||
ordered-messages [message-4
|
||||
message-3
|
||||
message-2
|
||||
message-1]
|
||||
[m1 d1 m2 m3 m4 d2] (db/add-datemarks ordered-messages)]
|
||||
(is (= "Jan 1, 2000"
|
||||
(:datemark m1)))
|
||||
(is (= {:type :datemark
|
||||
:value "Jan 1, 2000"}
|
||||
d1))
|
||||
(is (= "Dec 31, 1999"
|
||||
(:datemark m2)
|
||||
(:datemark m3)
|
||||
(:datemark m4)))
|
||||
(is (= {:type :datemark
|
||||
:value "Dec 31, 1999"}
|
||||
d2)))))
|
@ -1,344 +1,12 @@
|
||||
(ns status-im.chat.models
|
||||
(:require [clojure.set :as set]
|
||||
[quo.design-system.colors :as colors]
|
||||
(:require [utils.i18n :as i18n]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.add-new.db :as new-public-chat.db]
|
||||
[status-im.chat.models.loading :as loading]
|
||||
[status-im2.contexts.chat.messages.list.events :as message-list]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im.data-store.chats :as chats-store]
|
||||
[status-im.data-store.contacts :as contacts-store]
|
||||
[utils.i18n :as i18n]
|
||||
[status-im.mailserver.core :as mailserver]
|
||||
[status-im.multiaccounts.model :as multiaccounts.model]
|
||||
[status-im2.contexts.chat.messages.list.state :as chat.state]
|
||||
[status-im.utils.clocks :as utils.clocks]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im2.contexts.chat.messages.delete-message-for-me.events :as delete-for-me]
|
||||
[status-im2.contexts.chat.messages.delete-message.events :as delete-message]
|
||||
[status-im2.navigation.events :as navigation]
|
||||
[taoensso.timbre :as log]))
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.add-new.db :as new-public-chat.db]
|
||||
[status-im.data-store.chats :as chats-store]))
|
||||
|
||||
(defn- get-chat
|
||||
[cofx chat-id]
|
||||
(get-in cofx [:db :chats chat-id]))
|
||||
|
||||
(defn multi-user-chat?
|
||||
([chat]
|
||||
(:group-chat chat))
|
||||
([cofx chat-id]
|
||||
(multi-user-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(def one-to-one-chat?
|
||||
(complement multi-user-chat?))
|
||||
|
||||
(defn public-chat?
|
||||
([chat]
|
||||
(:public? chat))
|
||||
([cofx chat-id]
|
||||
(public-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn community-chat?
|
||||
([{:keys [chat-type]}]
|
||||
(= chat-type constants/community-chat-type))
|
||||
([cofx chat-id]
|
||||
(community-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn active-chat?
|
||||
[cofx chat-id]
|
||||
(let [chat (get-chat cofx chat-id)]
|
||||
(:active chat)))
|
||||
|
||||
(defn foreground-chat?
|
||||
[{{:keys [current-chat-id view-id]} :db} chat-id]
|
||||
(and (= current-chat-id chat-id)
|
||||
(= view-id :chat)))
|
||||
|
||||
(defn group-chat?
|
||||
([chat]
|
||||
(and (multi-user-chat? chat)
|
||||
(not (public-chat? chat))))
|
||||
([cofx chat-id]
|
||||
(group-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn timeline-chat?
|
||||
([chat]
|
||||
(:timeline? chat))
|
||||
([cofx chat-id]
|
||||
(timeline-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn profile-chat?
|
||||
([chat]
|
||||
(:profile-public-key chat))
|
||||
([cofx chat-id]
|
||||
(profile-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn set-chat-ui-props
|
||||
"Updates ui-props in active chat by merging provided kvs into them"
|
||||
[{:keys [current-chat-id] :as db} kvs]
|
||||
(update-in db [:chat-ui-props current-chat-id] merge kvs))
|
||||
|
||||
(defn- create-new-chat
|
||||
[chat-id {:keys [db now]}]
|
||||
(let [name (get-in db [:contacts/contacts chat-id :name])]
|
||||
{:chat-id chat-id
|
||||
:name (or name "")
|
||||
:color (rand-nth colors/chat-colors)
|
||||
:chat-type constants/one-to-one-chat-type
|
||||
:group-chat false
|
||||
:timestamp now
|
||||
:contacts #{chat-id}
|
||||
:last-clock-value 0}))
|
||||
|
||||
(defn map-chats
|
||||
[{:keys [db] :as cofx}]
|
||||
(fn [val]
|
||||
(assoc
|
||||
(merge
|
||||
(or (get (:chats db) (:chat-id val))
|
||||
(create-new-chat (:chat-id val) cofx))
|
||||
val)
|
||||
:invitation-admin
|
||||
(:invitation-admin val))))
|
||||
|
||||
(defn filter-chats
|
||||
[db]
|
||||
(fn [val]
|
||||
(and (not (get-in db [:chats (:chat-id val)])) (:public? val))))
|
||||
|
||||
(rf/defn leave-removed-chat
|
||||
[{{:keys [view-id current-chat-id chats]} :db
|
||||
:as cofx}]
|
||||
(when (and (= view-id :chat)
|
||||
(not (contains? chats current-chat-id)))
|
||||
(navigation/navigate-back cofx)))
|
||||
|
||||
(rf/defn ensure-chats
|
||||
"Add chats to db and update"
|
||||
[{:keys [db] :as cofx} chats]
|
||||
(let [{:keys [all-chats chats-home-list removed-chats]}
|
||||
(reduce
|
||||
(fn [acc {:keys [chat-id profile-public-key timeline? community-id active muted] :as chat}]
|
||||
(if (not (or active muted))
|
||||
(update acc :removed-chats conj chat-id)
|
||||
(cond-> acc
|
||||
(and (not profile-public-key) (not timeline?) (not community-id) active)
|
||||
(update :chats-home-list conj chat-id)
|
||||
:always
|
||||
(assoc-in [:all-chats chat-id] chat))))
|
||||
{:all-chats {}
|
||||
:chats-home-list #{}
|
||||
:removed-chats #{}}
|
||||
(map (map-chats cofx) chats))]
|
||||
(rf/merge
|
||||
cofx
|
||||
(merge {:db (-> db
|
||||
(update :chats merge all-chats)
|
||||
(update :chats-home-list set/union chats-home-list)
|
||||
(update :chats #(apply dissoc % removed-chats))
|
||||
(update :chats-home-list set/difference removed-chats))}
|
||||
(when (not-empty removed-chats)
|
||||
{:clear-message-notifications
|
||||
[removed-chats
|
||||
(get-in db [:multiaccount :remote-push-notifications-enabled?])]}))
|
||||
leave-removed-chat)))
|
||||
|
||||
(rf/defn clear-history
|
||||
"Clears history of the particular chat"
|
||||
[{:keys [db] :as cofx} chat-id remove-chat?]
|
||||
(let [{:keys [last-message public?
|
||||
deleted-at-clock-value]}
|
||||
(get-in db [:chats chat-id])
|
||||
last-message-clock-value (if (and public? remove-chat?)
|
||||
0
|
||||
(or (:clock-value last-message)
|
||||
deleted-at-clock-value
|
||||
(utils.clocks/send 0)))]
|
||||
{:db (-> db
|
||||
(assoc-in [:messages chat-id] {})
|
||||
(update-in [:message-lists] dissoc chat-id)
|
||||
(update :chats
|
||||
(fn [chats]
|
||||
(if (contains? chats chat-id)
|
||||
(update chats
|
||||
chat-id
|
||||
merge
|
||||
{:last-message nil
|
||||
:unviewed-messages-count 0
|
||||
:unviewed-mentions-count 0
|
||||
:deleted-at-clock-value last-message-clock-value})
|
||||
chats))))}))
|
||||
|
||||
(rf/defn clear-history-handler
|
||||
"Clears history of the particular chat"
|
||||
{:events [:chat.ui/clear-history]}
|
||||
[{:keys [db] :as cofx} chat-id remove-chat?]
|
||||
(rf/merge cofx
|
||||
{:db db
|
||||
:json-rpc/call [{:method "wakuext_clearHistory"
|
||||
:params [{:id chat-id}]
|
||||
:on-success #(re-frame/dispatch [::history-cleared chat-id %])
|
||||
:on-error #(log/error "failed to clear history " chat-id %)}]}
|
||||
(clear-history chat-id remove-chat?)))
|
||||
|
||||
(rf/defn chat-deactivated
|
||||
{:events [::chat-deactivated]}
|
||||
[_ chat-id]
|
||||
(log/debug "chat deactivated" chat-id))
|
||||
|
||||
(rf/defn deactivate-chat
|
||||
"Deactivate chat in db, no side effects"
|
||||
[{:keys [db now] :as cofx} chat-id]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (-> (if (get-in db [:chats chat-id :muted])
|
||||
(assoc-in db [:chats chat-id :active] false)
|
||||
(update db :chats dissoc chat-id))
|
||||
(update :chats-home-list disj chat-id)
|
||||
(assoc :current-chat-id nil))
|
||||
:json-rpc/call [{:method "wakuext_deactivateChat"
|
||||
:params [{:id chat-id}]
|
||||
:on-success #(re-frame/dispatch [::chat-deactivated chat-id])
|
||||
:on-error #(log/error "failed to create public chat" chat-id %)}]}
|
||||
(clear-history chat-id true)))
|
||||
|
||||
(rf/defn offload-messages
|
||||
{:events [:offload-messages]}
|
||||
[{:keys [db]} chat-id]
|
||||
(merge {:db (-> db
|
||||
(update :messages dissoc chat-id)
|
||||
(update :message-lists dissoc chat-id)
|
||||
(update :pagination-info dissoc chat-id))}
|
||||
(when (and (= chat-id constants/timeline-chat-id) (= (:view-id db) :status))
|
||||
{:dispatch [:init-timeline-chat]})))
|
||||
|
||||
(rf/defn close-chat
|
||||
{:events [:close-chat]}
|
||||
[{:keys [db] :as cofx} navigate-to-shell?]
|
||||
(when-let [chat-id (:current-chat-id db)]
|
||||
(chat.state/reset-visible-item)
|
||||
(rf/merge cofx
|
||||
(merge
|
||||
{:db (dissoc db :current-chat-id)}
|
||||
(let [community-id (get-in db [:chats chat-id :community-id])]
|
||||
;; When navigating back from community chat to community, update switcher card
|
||||
(when (and community-id (not navigate-to-shell?))
|
||||
{:dispatch [:shell/add-switcher-card
|
||||
:community {:community-id community-id}]})))
|
||||
(delete-for-me/sync-all)
|
||||
(delete-message/send-all)
|
||||
(offload-messages chat-id))))
|
||||
|
||||
(rf/defn force-close-chat
|
||||
[{:keys [db] :as cofx} chat-id]
|
||||
(do
|
||||
(chat.state/reset-visible-item)
|
||||
(rf/merge cofx
|
||||
{:db (dissoc db :current-chat-id)}
|
||||
(offload-messages chat-id))))
|
||||
|
||||
(rf/defn remove-chat
|
||||
"Removes chat completely from app, producing all necessary effects for that"
|
||||
{:events [:chat.ui/remove-chat]}
|
||||
[{:keys [db now] :as cofx} chat-id]
|
||||
(rf/merge cofx
|
||||
{:clear-message-notifications
|
||||
[[chat-id] (get-in db [:multiaccount :remote-push-notifications-enabled?])]
|
||||
:dispatch [:shell/close-switcher-card chat-id]}
|
||||
(deactivate-chat chat-id)
|
||||
(offload-messages chat-id)))
|
||||
|
||||
(rf/defn show-more-chats
|
||||
{:events [:chat.ui/show-more-chats]}
|
||||
[{:keys [db]}]
|
||||
(when (< (:home-items-show-number db) (count (:chats db)))
|
||||
{:db (update db :home-items-show-number + 40)}))
|
||||
|
||||
(rf/defn preload-chat-data
|
||||
"Takes chat-id and coeffects map, returns effects necessary when navigating to chat"
|
||||
{:events [:chat.ui/preload-chat-data]}
|
||||
[cofx chat-id]
|
||||
(loading/load-messages cofx chat-id))
|
||||
|
||||
(rf/defn navigate-to-chat
|
||||
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
|
||||
{:events [:chat.ui/navigate-to-chat]}
|
||||
[{db :db :as cofx} chat-id]
|
||||
(rf/merge cofx
|
||||
{:dispatch [:navigate-to :chat]}
|
||||
(navigation/change-tab :chat)
|
||||
(when-not (= (:view-id db) :community)
|
||||
(navigation/pop-to-root-tab :chat-stack))
|
||||
(close-chat false)
|
||||
(force-close-chat chat-id)
|
||||
(fn [{:keys [db]}]
|
||||
{:db (assoc db :current-chat-id chat-id)})
|
||||
(preload-chat-data chat-id)
|
||||
#(when (group-chat? cofx chat-id)
|
||||
(loading/load-chat % chat-id))))
|
||||
|
||||
(rf/defn navigate-to-chat-nav2
|
||||
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
|
||||
{:events [:chat.ui/navigate-to-chat-nav2]}
|
||||
[{db :db :as cofx} chat-id from-shell?]
|
||||
(rf/merge cofx
|
||||
{:dispatch [:navigate-to-nav2 :chat chat-id from-shell?]}
|
||||
(when-not (= (:view-id db) :community)
|
||||
(navigation/pop-to-root-tab :shell-stack))
|
||||
(close-chat false)
|
||||
(force-close-chat chat-id)
|
||||
(fn [{:keys [db]}]
|
||||
{:db (assoc db :current-chat-id chat-id)})
|
||||
(preload-chat-data chat-id)
|
||||
#(when (group-chat? cofx chat-id)
|
||||
(loading/load-chat % chat-id))))
|
||||
|
||||
(rf/defn handle-clear-history-response
|
||||
{:events [::history-cleared]}
|
||||
[{:keys [db]} chat-id response]
|
||||
(let [chat (chats-store/<-rpc (first (:chats response)))]
|
||||
{:db (assoc-in db [:chats chat-id] chat)}))
|
||||
|
||||
(rf/defn handle-one-to-one-chat-created
|
||||
{:events [::one-to-one-chat-created]}
|
||||
[{:keys [db]} chat-id response]
|
||||
(let [chat (chats-store/<-rpc (first (:chats response)))
|
||||
contact-rpc (first (:contacts response))
|
||||
contact (when contact-rpc (contacts-store/<-rpc contact-rpc))]
|
||||
{:db (cond-> db
|
||||
contact
|
||||
(assoc-in [:contacts/contacts chat-id] contact)
|
||||
:always
|
||||
(assoc-in [:chats chat-id] chat)
|
||||
:always
|
||||
(update :chats-home-list conj chat-id))
|
||||
:dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]}))
|
||||
|
||||
(rf/defn navigate-to-user-pinned-messages
|
||||
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
|
||||
{:events [:chat.ui/navigate-to-pinned-messages]}
|
||||
[cofx chat-id]
|
||||
(navigation/navigate-to cofx :chat-pinned-messages {:chat-id chat-id}))
|
||||
|
||||
(rf/defn start-chat
|
||||
"Start a chat, making sure it exists"
|
||||
{:events [:chat.ui/start-chat]}
|
||||
[{:keys [db] :as cofx} chat-id ens-name]
|
||||
;; don't allow to open chat with yourself
|
||||
(when (not= (multiaccounts.model/current-public-key cofx) chat-id)
|
||||
{:json-rpc/call [{:method "wakuext_createOneToOneChat"
|
||||
:params [{:id chat-id :ensName ens-name}]
|
||||
:on-success #(re-frame/dispatch [::one-to-one-chat-created chat-id %])
|
||||
:on-error #(log/error "failed to create one-to-on chat" chat-id %)}]}))
|
||||
|
||||
(defn profile-chat-topic
|
||||
[public-key]
|
||||
(str "@" public-key))
|
||||
|
||||
(defn my-profile-chat-topic
|
||||
[db]
|
||||
(profile-chat-topic (get-in db [:multiaccount :public-key])))
|
||||
;; OLD
|
||||
|
||||
(rf/defn handle-public-chat-created
|
||||
{:events [::public-chat-created]}
|
||||
@ -346,7 +14,7 @@
|
||||
{:db (-> db
|
||||
(assoc-in [:chats chat-id] (chats-store/<-rpc (first (:chats response))))
|
||||
(update :chats-home-list conj chat-id))
|
||||
:dispatch [:chat.ui/navigate-to-chat chat-id]})
|
||||
:dispatch [:chat/navigate-to-chat chat-id]})
|
||||
|
||||
(rf/defn create-public-chat-go
|
||||
[_ chat-id]
|
||||
@ -360,174 +28,8 @@
|
||||
{:events [:chat.ui/start-public-chat]}
|
||||
[cofx topic]
|
||||
(if (new-public-chat.db/valid-topic? topic)
|
||||
(if (active-chat? cofx topic)
|
||||
(navigate-to-chat cofx topic)
|
||||
(create-public-chat-go
|
||||
cofx
|
||||
topic))
|
||||
(create-public-chat-go
|
||||
cofx
|
||||
topic)
|
||||
{:utils/show-popup {:title (i18n/label :t/cant-open-public-chat)
|
||||
:content (i18n/label :t/invalid-public-chat-topic)}}))
|
||||
|
||||
(rf/defn profile-chat-created
|
||||
{:events [::profile-chat-created]}
|
||||
[{:keys [db] :as cofx} chat-id response navigate-to?]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db db}
|
||||
#(when response
|
||||
(let [chat (chats-store/<-rpc (first (:chats response)))]
|
||||
{:db (assoc-in db [:chats chat-id] chat)}))
|
||||
#(when navigate-to?
|
||||
{:dispatch-n [[:chat.ui/preload-chat-data chat-id]
|
||||
[:open-modal :profile]]})))
|
||||
|
||||
(rf/defn start-profile-chat
|
||||
"Starts a new profile chat"
|
||||
{:events [:start-profile-chat]}
|
||||
[cofx profile-public-key navigate-to?]
|
||||
(let [chat-id (profile-chat-topic profile-public-key)]
|
||||
(if (active-chat? cofx chat-id)
|
||||
{:dispatch [::profile-chat-created chat-id nil navigate-to?]}
|
||||
{:json-rpc/call [{:method "wakuext_createProfileChat"
|
||||
:params [{:id profile-public-key}]
|
||||
:on-success #(re-frame/dispatch [::profile-chat-created chat-id % navigate-to?])
|
||||
:on-error #(log/error "failed to create profile chat" chat-id %)}]})))
|
||||
|
||||
(rf/defn disable-chat-cooldown
|
||||
"Turns off chat cooldown (protection against message spamming)"
|
||||
{:events [:chat/disable-cooldown]}
|
||||
[{:keys [db]}]
|
||||
{:db (assoc db :chat/cooldown-enabled? false)})
|
||||
|
||||
;; effects
|
||||
(re-frame/reg-fx
|
||||
:show-cooldown-warning
|
||||
(fn [_]
|
||||
(utils/show-popup nil
|
||||
(i18n/label :cooldown/warning-message)
|
||||
#())))
|
||||
|
||||
(rf/defn mute-chat-failed
|
||||
{:events [::mute-chat-failed]}
|
||||
[{:keys [db] :as cofx} chat-id muted? error]
|
||||
(log/error "mute chat failed" chat-id error)
|
||||
{:db (assoc-in db [:chats chat-id :muted] (not muted?))})
|
||||
|
||||
(rf/defn mute-chat-toggled-successfully
|
||||
{:events [::mute-chat-toggled-successfully]}
|
||||
[_ chat-id]
|
||||
(log/debug "muted chat successfully" chat-id))
|
||||
|
||||
(rf/defn mute-chat
|
||||
{:events [::mute-chat-toggled]}
|
||||
[{:keys [db] :as cofx} chat-id muted?]
|
||||
(let [method (if muted? "wakuext_muteChat" "wakuext_unmuteChat")]
|
||||
{:db (assoc-in db [:chats chat-id :muted] muted?)
|
||||
:json-rpc/call [{:method method
|
||||
:params [chat-id]
|
||||
:on-error #(re-frame/dispatch [::mute-chat-failed chat-id muted? %])
|
||||
:on-success #(re-frame/dispatch [::mute-chat-toggled-successfully chat-id])}]}))
|
||||
|
||||
(rf/defn show-profile
|
||||
{:events [:chat.ui/show-profile]}
|
||||
[{:keys [db] :as cofx} identity ens-name]
|
||||
(let [my-public-key (get-in db [:multiaccount :public-key])]
|
||||
(when (not= my-public-key identity)
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (-> db
|
||||
(assoc :contacts/identity identity)
|
||||
(assoc :contacts/ens-name ens-name))}
|
||||
(start-profile-chat identity true)))))
|
||||
|
||||
(rf/defn clear-history-pressed
|
||||
{:events [:chat.ui/clear-history-pressed]}
|
||||
[_ chat-id]
|
||||
{:ui/show-confirmation
|
||||
{:title (i18n/label :t/clear-history-title)
|
||||
:content (i18n/label :t/clear-history-confirmation-content)
|
||||
:confirm-button-text (i18n/label :t/clear-history-action)
|
||||
:on-accept #(do
|
||||
(re-frame/dispatch [:bottom-sheet/hide])
|
||||
(re-frame/dispatch [:chat.ui/clear-history chat-id false]))}})
|
||||
|
||||
(rf/defn gaps-failed
|
||||
{:events [::gaps-failed]}
|
||||
[{:keys [db]} chat-id gap-ids error]
|
||||
(log/error "failed to fetch gaps" chat-id gap-ids error)
|
||||
{:db (dissoc db :mailserver/fetching-gaps-in-progress)})
|
||||
|
||||
(rf/defn sync-chat-from-sync-from-failed
|
||||
{:events [::sync-chat-from-sync-from-failed]}
|
||||
[{:keys [db]} chat-id error]
|
||||
(log/error "failed to sync chat" chat-id error)
|
||||
{:db (dissoc db :mailserver/fetching-gaps-in-progress)})
|
||||
|
||||
(rf/defn sync-chat-from-sync-from-success
|
||||
{:events [::sync-chat-from-sync-from-success]}
|
||||
[{:keys [db] :as cofx} chat-id synced-from]
|
||||
(log/debug "synced success" chat-id synced-from)
|
||||
{:db
|
||||
(-> db
|
||||
(assoc-in [:chats chat-id :synced-from] synced-from)
|
||||
(dissoc :mailserver/fetching-gaps-in-progress))})
|
||||
|
||||
(rf/defn gaps-filled
|
||||
{:events [::gaps-filled]}
|
||||
[{:keys [db] :as cofx} chat-id message-ids]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (-> db
|
||||
(update-in [:messages chat-id] (fn [messages] (apply dissoc messages message-ids)))
|
||||
(dissoc :mailserver/fetching-gaps-in-progress))}
|
||||
(message-list/rebuild-message-list chat-id)))
|
||||
|
||||
(rf/defn fill-gaps
|
||||
[cofx chat-id gap-ids]
|
||||
{:json-rpc/call [{:method "wakuext_fillGaps"
|
||||
:params [chat-id gap-ids]
|
||||
:on-success #(re-frame/dispatch [::gaps-filled chat-id gap-ids %])
|
||||
:on-error #(re-frame/dispatch [::gaps-failed chat-id gap-ids %])}]})
|
||||
|
||||
(rf/defn sync-chat-from-sync-from
|
||||
[cofx chat-id]
|
||||
(log/debug "syncing chat from sync from")
|
||||
{:json-rpc/call [{:method "wakuext_syncChatFromSyncedFrom"
|
||||
:params [chat-id]
|
||||
:on-success #(re-frame/dispatch [::sync-chat-from-sync-from-success chat-id %])
|
||||
:on-error #(re-frame/dispatch [::sync-chat-from-sync-from-failed chat-id %])}]})
|
||||
|
||||
(rf/defn chat-ui-fill-gaps
|
||||
{:events [:chat.ui/fill-gaps]}
|
||||
[{:keys [db] :as cofx} chat-id gap-ids]
|
||||
(let [use-status-nodes? (mailserver/fetch-use-mailservers? {:db db})]
|
||||
(log/info "filling gaps if use-status-nodes = true" chat-id gap-ids)
|
||||
(when use-status-nodes?
|
||||
(rf/merge cofx
|
||||
{:db (assoc db :mailserver/fetching-gaps-in-progress gap-ids)}
|
||||
(if (= gap-ids #{:first-gap})
|
||||
(sync-chat-from-sync-from chat-id)
|
||||
(fill-gaps chat-id gap-ids))))))
|
||||
|
||||
(rf/defn chat-ui-remove-chat-pressed
|
||||
{:events [:chat.ui/remove-chat-pressed]}
|
||||
[_ chat-id]
|
||||
{:ui/show-confirmation
|
||||
{:title (i18n/label :t/delete-confirmation)
|
||||
:content (i18n/label :t/delete-chat-confirmation)
|
||||
:confirm-button-text (i18n/label :t/delete)
|
||||
:on-accept #(do
|
||||
(re-frame/dispatch [:bottom-sheet/hide])
|
||||
(re-frame/dispatch [:chat.ui/remove-chat chat-id]))}})
|
||||
|
||||
(rf/defn decrease-unviewed-count
|
||||
{:events [:chat/decrease-unviewed-count]}
|
||||
[{:keys [db]} chat-id {:keys [count countWithMentions]}]
|
||||
{:db (-> db
|
||||
;; There might be some other requests being fired,
|
||||
;; so we need to make sure the count has not been set to
|
||||
;; 0 in the meantime
|
||||
(update-in [:chats chat-id :unviewed-messages-count]
|
||||
#(max (- % count) 0))
|
||||
(update-in [:chats chat-id :unviewed-mentions-count]
|
||||
#(max (- % countWithMentions) 0)))})
|
||||
|
63
src/status_im/chat/models/gaps.cljs
Normal file
63
src/status_im/chat/models/gaps.cljs
Normal file
@ -0,0 +1,63 @@
|
||||
(ns status-im.chat.models.gaps
|
||||
(:require [utils.re-frame :as rf]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im2.contexts.chat.messages.list.events :as message-list]
|
||||
[status-im.mailserver.core :as mailserver]))
|
||||
|
||||
(rf/defn gaps-filled
|
||||
{:events [:gaps/filled]}
|
||||
[{:keys [db] :as cofx} chat-id message-ids]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (-> db
|
||||
(update-in [:messages chat-id] (fn [messages] (apply dissoc messages message-ids)))
|
||||
(dissoc :mailserver/fetching-gaps-in-progress))}
|
||||
(message-list/rebuild-message-list chat-id)))
|
||||
|
||||
(rf/defn gaps-failed
|
||||
{:events [:gaps/failed]}
|
||||
[{:keys [db]} chat-id gap-ids error]
|
||||
(log/error "failed to fetch gaps" chat-id gap-ids error)
|
||||
{:db (dissoc db :mailserver/fetching-gaps-in-progress)})
|
||||
|
||||
(rf/defn sync-chat-from-sync-from-failed
|
||||
{:events [::sync-chat-from-sync-from-failed]}
|
||||
[{:keys [db]} chat-id error]
|
||||
(log/error "failed to sync chat" chat-id error)
|
||||
{:db (dissoc db :mailserver/fetching-gaps-in-progress)})
|
||||
|
||||
(rf/defn sync-chat-from-sync-from-success
|
||||
{:events [::sync-chat-from-sync-from-success]}
|
||||
[{:keys [db] :as cofx} chat-id synced-from]
|
||||
(log/debug "synced success" chat-id synced-from)
|
||||
{:db
|
||||
(-> db
|
||||
(assoc-in [:chats chat-id :synced-from] synced-from)
|
||||
(dissoc :mailserver/fetching-gaps-in-progress))})
|
||||
|
||||
(rf/defn fill-gaps
|
||||
[_ chat-id gap-ids]
|
||||
{:json-rpc/call [{:method "wakuext_fillGaps"
|
||||
:params [chat-id gap-ids]
|
||||
:on-success #(rf/dispatch [:gaps/filled chat-id gap-ids %])
|
||||
:on-error #(rf/dispatch [:gaps/failed chat-id gap-ids %])}]})
|
||||
|
||||
(rf/defn sync-chat-from-sync-from
|
||||
[_ chat-id]
|
||||
(log/debug "syncing chat from sync from")
|
||||
{:json-rpc/call [{:method "wakuext_syncChatFromSyncedFrom"
|
||||
:params [chat-id]
|
||||
:on-success #(rf/dispatch [::sync-chat-from-sync-from-success chat-id %])
|
||||
:on-error #(rf/dispatch [::sync-chat-from-sync-from-failed chat-id %])}]})
|
||||
|
||||
(rf/defn chat-ui-fill-gaps
|
||||
{:events [:chat.ui/fill-gaps]}
|
||||
[{:keys [db] :as cofx} chat-id gap-ids]
|
||||
(let [use-status-nodes? (mailserver/fetch-use-mailservers? {:db db})]
|
||||
(log/info "filling gaps if use-status-nodes = true" chat-id gap-ids)
|
||||
(when use-status-nodes?
|
||||
(rf/merge cofx
|
||||
{:db (assoc db :mailserver/fetching-gaps-in-progress gap-ids)}
|
||||
(if (= gap-ids #{:first-gap})
|
||||
(sync-chat-from-sync-from chat-id)
|
||||
(fill-gaps chat-id gap-ids))))))
|
@ -3,7 +3,6 @@
|
||||
["react-native-blob-util" :default ReactNativeBlobUtil]
|
||||
[clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.models :as chat]
|
||||
[utils.i18n :as i18n]
|
||||
[status-im.ui.components.permissions :as permissions]
|
||||
[status-im.ui.components.react :as react]
|
||||
@ -167,11 +166,6 @@
|
||||
(let [current-chat-id (or chat-id (:current-chat-id db))]
|
||||
(clear-sending-images cofx current-chat-id)))
|
||||
|
||||
(rf/defn cancel-sending-image-timeline
|
||||
{:events [:chat.ui/cancel-sending-image-timeline]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(cancel-sending-image cofx (chat/my-profile-chat-topic db)))
|
||||
|
||||
(rf/defn image-selected
|
||||
{:events [:chat.ui/image-selected]}
|
||||
[{:keys [db]} current-chat-id original uri]
|
||||
@ -191,11 +185,6 @@
|
||||
(when (< (count images) config/max-images-batch)
|
||||
{::chat-open-image-picker current-chat-id})))
|
||||
|
||||
(rf/defn chat-open-image-picker-timeline
|
||||
{:events [:chat.ui/open-image-picker-timeline]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(chat-open-image-picker cofx (chat/my-profile-chat-topic db)))
|
||||
|
||||
(rf/defn chat-show-image-picker-camera
|
||||
{:events [:chat.ui/show-image-picker-camera]}
|
||||
[{:keys [db]} chat-id]
|
||||
@ -204,11 +193,6 @@
|
||||
(when (< (count images) config/max-images-batch)
|
||||
{::chat-open-image-picker-camera current-chat-id})))
|
||||
|
||||
(rf/defn chat-show-image-picker-camera-timeline
|
||||
{:events [:chat.ui/show-image-picker-camera-timeline]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(chat-show-image-picker-camera cofx (chat/my-profile-chat-topic db)))
|
||||
|
||||
(rf/defn camera-roll-pick
|
||||
{:events [:chat.ui/camera-roll-pick]}
|
||||
[{:keys [db]} uri chat-id]
|
||||
@ -221,11 +205,6 @@
|
||||
(not (get images uri)))
|
||||
{::image-selected [uri current-chat-id]}))))
|
||||
|
||||
(rf/defn camera-roll-pick-timeline
|
||||
{:events [:chat.ui/camera-roll-pick-timeline]}
|
||||
[{:keys [db] :as cofx} uri]
|
||||
(camera-roll-pick cofx uri (chat/my-profile-chat-topic db)))
|
||||
|
||||
(rf/defn save-image-to-gallery
|
||||
{:events [:chat.ui/save-image-to-gallery]}
|
||||
[_ base64-uri]
|
||||
|
@ -3,14 +3,12 @@
|
||||
[clojure.string :as string]
|
||||
[goog.object :as object]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im.chat.models.mentions :as mentions]
|
||||
[status-im.chat.models.message :as chat.message]
|
||||
[status-im.chat.models.message-content :as message-content]
|
||||
[status-im2.constants :as constants]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.datetime :as datetime]
|
||||
[status-im.utils.utils :as utils]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.ui.screens.chat.components.input :as input]))
|
||||
@ -25,6 +23,14 @@
|
||||
(.-char ^js emoji-map)
|
||||
original))))
|
||||
|
||||
;; effects
|
||||
(re-frame/reg-fx
|
||||
:show-cooldown-warning
|
||||
(fn [_]
|
||||
(utils/show-popup nil
|
||||
(i18n/label :cooldown/warning-message)
|
||||
#())))
|
||||
|
||||
(rf/defn set-chat-input-text
|
||||
"Set input text for current-chat. Takes db and input text and cofx
|
||||
as arguments and returns new fx. Always clear all validation messages."
|
||||
@ -33,13 +39,6 @@
|
||||
(let [current-chat-id (or chat-id (:current-chat-id db))]
|
||||
{:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))}))
|
||||
|
||||
(rf/defn set-timeline-input-text
|
||||
{:events [:chat.ui/set-timeline-input-text]}
|
||||
[{db :db} new-input]
|
||||
{:db (assoc-in db
|
||||
[:chat/inputs (chat/my-profile-chat-topic db) :input-text]
|
||||
(text->emoji new-input))})
|
||||
|
||||
(rf/defn select-mention
|
||||
{:events [:chat.ui/select-mention]}
|
||||
[{:keys [db] :as cofx} text-input-ref {:keys [alias name searched-text match] :as user}]
|
||||
@ -74,45 +73,11 @@
|
||||
:end end}))
|
||||
(mentions/recheck-at-idxs {alias user}))))
|
||||
|
||||
(defn- start-cooldown
|
||||
[{:keys [db]} cooldowns]
|
||||
{:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||
:ms (constants/cooldown-periods-ms
|
||||
cooldowns
|
||||
constants/default-cooldown-period-ms)}]
|
||||
:show-cooldown-warning nil
|
||||
:db (assoc db
|
||||
:chat/cooldowns (if
|
||||
(=
|
||||
constants/cooldown-reset-threshold
|
||||
cooldowns)
|
||||
0
|
||||
cooldowns)
|
||||
:chat/spam-messages-frequency 0
|
||||
:chat/cooldown-enabled? true)})
|
||||
|
||||
(rf/defn process-cooldown
|
||||
"Process cooldown to protect against message spammers"
|
||||
[{{:keys [chat/last-outgoing-message-sent-at
|
||||
chat/cooldowns
|
||||
chat/spam-messages-frequency
|
||||
current-chat-id]
|
||||
:as db}
|
||||
:db
|
||||
:as cofx}]
|
||||
(when (chat/public-chat? cofx current-chat-id)
|
||||
(let [spamming-fast? (< (- (datetime/timestamp) last-outgoing-message-sent-at)
|
||||
(+ constants/spam-interval-ms (* 1000 cooldowns)))
|
||||
spamming-frequently? (= constants/spam-message-frequency-threshold
|
||||
spam-messages-frequency)]
|
||||
(cond-> {:db (assoc db
|
||||
:chat/last-outgoing-message-sent-at (datetime/timestamp)
|
||||
:chat/spam-messages-frequency (if spamming-fast?
|
||||
(inc spam-messages-frequency)
|
||||
0))}
|
||||
|
||||
(and spamming-fast? spamming-frequently?)
|
||||
(start-cooldown (inc cooldowns))))))
|
||||
(rf/defn disable-chat-cooldown
|
||||
"Turns off chat cooldown (protection against message spamming)"
|
||||
{:events [:chat/disable-cooldown]}
|
||||
[{:keys [db]}]
|
||||
{:db (assoc db :chat/cooldown-enabled? false)})
|
||||
|
||||
(rf/defn reply-to-message
|
||||
"Sets reference to previous chat message and focuses on input"
|
||||
@ -222,21 +187,6 @@
|
||||
(when (seq messages)
|
||||
(rf/merge cofx
|
||||
(clean-input (:current-chat-id db))
|
||||
(process-cooldown)
|
||||
(chat.message/send-messages messages)))))
|
||||
|
||||
(rf/defn send-my-status-message
|
||||
"when not empty, proceed by sending text message with public key topic"
|
||||
{:events [:profile.ui/send-my-status-message]}
|
||||
[{db :db :as cofx}]
|
||||
(let [current-chat-id (chat/my-profile-chat-topic db)
|
||||
{:keys [input-text]} (get-in db [:chat/inputs current-chat-id])
|
||||
image-messages (build-image-messages cofx current-chat-id)
|
||||
text-message (build-text-message cofx input-text current-chat-id)
|
||||
messages (keep identity (conj image-messages text-message))]
|
||||
(when (seq messages)
|
||||
(rf/merge cofx
|
||||
(clean-input current-chat-id)
|
||||
(chat.message/send-messages messages)))))
|
||||
|
||||
(rf/defn send-audio-message
|
||||
@ -274,8 +224,7 @@
|
||||
:js-response true
|
||||
:on-error #(log/error "failed to edit message " %)
|
||||
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]}
|
||||
(cancel-message-edit)
|
||||
(process-cooldown)))
|
||||
(cancel-message-edit)))
|
||||
|
||||
(rf/defn send-current-message
|
||||
"Sends message from current chat input"
|
||||
@ -304,8 +253,7 @@
|
||||
:on-success #(re-frame/dispatch [:transport/message-sent %])}]}
|
||||
(mentions/clear-mentions)
|
||||
(mentions/clear-cursor)
|
||||
(clean-input (:current-chat-id db))
|
||||
(process-cooldown)))
|
||||
(clean-input (:current-chat-id db))))
|
||||
|
||||
(rf/defn cancel-contact-request
|
||||
"Cancels contact request"
|
||||
@ -316,8 +264,7 @@
|
||||
{:db (assoc-in db [:chat/inputs current-chat-id :metadata :sending-contact-request] nil)}
|
||||
(mentions/clear-mentions)
|
||||
(mentions/clear-cursor)
|
||||
(clean-input (:current-chat-id db))
|
||||
(process-cooldown))))
|
||||
(clean-input (:current-chat-id db)))))
|
||||
|
||||
(rf/defn chat-send-sticker
|
||||
{:events [:chat/send-sticker]}
|
||||
|
@ -1,79 +1,9 @@
|
||||
(ns status-im.chat.models.input-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im.chat.models.input :as input]
|
||||
[utils.datetime :as datetime]))
|
||||
(:require [cljs.test :refer-macros [deftest is]]
|
||||
[status-im.chat.models.input :as input]))
|
||||
|
||||
(deftest text->emoji
|
||||
(is (nil? (input/text->emoji nil)))
|
||||
(is (= "" (input/text->emoji "")))
|
||||
(is (= "test" (input/text->emoji "test")))
|
||||
(is (= "word1 \uD83D\uDC4D word2" (input/text->emoji "word1 :+1: word2"))))
|
||||
|
||||
(deftest process-cooldown-fx
|
||||
(let [db {:current-chat-id "chat"
|
||||
:chats {"chat" {:public? true}}
|
||||
:chat/cooldowns 0
|
||||
:chat/spam-messages-frequency 0
|
||||
:chat/cooldown-enabled? false}]
|
||||
(with-redefs [datetime/timestamp (constantly 1527675198542)]
|
||||
(testing "no spamming detected"
|
||||
(let [expected {:db (assoc db :chat/last-outgoing-message-sent-at 1527675198542)}
|
||||
actual (input/process-cooldown {:db db})]
|
||||
(is (= expected actual))))
|
||||
|
||||
(testing "spamming detected in 1-1"
|
||||
(let [db (assoc db
|
||||
:chats {"chat" {:public? false}}
|
||||
:chat/spam-messages-frequency constants/spam-message-frequency-threshold
|
||||
:chat/last-outgoing-message-sent-at (- 1527675198542 900))
|
||||
expected nil
|
||||
actual (input/process-cooldown {:db db})]
|
||||
(is (= expected actual))))
|
||||
|
||||
(testing "spamming detected"
|
||||
(let [db (assoc db
|
||||
:chat/last-outgoing-message-sent-at (- 1527675198542 900)
|
||||
:chat/spam-messages-frequency constants/spam-message-frequency-threshold)
|
||||
expected {:db (assoc db
|
||||
:chat/last-outgoing-message-sent-at 1527675198542
|
||||
:chat/cooldowns 1
|
||||
:chat/spam-messages-frequency 0
|
||||
:chat/cooldown-enabled? true)
|
||||
:show-cooldown-warning nil
|
||||
:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||
:ms (constants/cooldown-periods-ms 1)}]}
|
||||
actual (input/process-cooldown {:db db})]
|
||||
(is (= expected actual))))
|
||||
|
||||
(testing "spamming detected twice"
|
||||
(let [db (assoc db
|
||||
:chat/cooldowns 1
|
||||
:chat/last-outgoing-message-sent-at (- 1527675198542 900)
|
||||
:chat/spam-messages-frequency constants/spam-message-frequency-threshold)
|
||||
expected {:db (assoc db
|
||||
:chat/last-outgoing-message-sent-at 1527675198542
|
||||
:chat/cooldowns 2
|
||||
:chat/spam-messages-frequency 0
|
||||
:chat/cooldown-enabled? true)
|
||||
:show-cooldown-warning nil
|
||||
:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||
:ms (constants/cooldown-periods-ms 2)}]}
|
||||
actual (input/process-cooldown {:db db})]
|
||||
(is (= expected actual))))
|
||||
|
||||
(testing "spamming reaching cooldown threshold"
|
||||
(let [db (assoc db
|
||||
:chat/cooldowns (dec constants/cooldown-reset-threshold)
|
||||
:chat/last-outgoing-message-sent-at (- 1527675198542 900)
|
||||
:chat/spam-messages-frequency constants/spam-message-frequency-threshold)
|
||||
expected {:db (assoc db
|
||||
:chat/last-outgoing-message-sent-at 1527675198542
|
||||
:chat/cooldowns 0
|
||||
:chat/spam-messages-frequency 0
|
||||
:chat/cooldown-enabled? true)
|
||||
:show-cooldown-warning nil
|
||||
:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||
:ms (constants/cooldown-periods-ms 3)}]}
|
||||
actual (input/process-cooldown {:db db})]
|
||||
(is (= expected actual)))))))
|
||||
|
@ -1,11 +1,9 @@
|
||||
(ns status-im.chat.models.message
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.models :as chat-model]
|
||||
[status-im.chat.models.loading :as chat.loading]
|
||||
[status-im.chat.models.mentions :as mentions]
|
||||
[status-im2.contexts.chat.messages.list.events :as message-list]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im.data-store.messages :as data-store.messages]
|
||||
[status-im.transport.message.protocol :as protocol]
|
||||
[status-im2.contexts.chat.messages.list.state :as view.state]
|
||||
@ -56,20 +54,6 @@
|
||||
{:db db}
|
||||
messages))
|
||||
|
||||
(defn timeline-message?
|
||||
[db chat-id]
|
||||
(and
|
||||
(get-in db [:pagination-info constants/timeline-chat-id :messages-initialized?])
|
||||
(or
|
||||
(= chat-id (chat-model/my-profile-chat-topic db))
|
||||
(when-let [pub-key (get-in db [:chats chat-id :profile-public-key])]
|
||||
(get-in db [:contacts/contacts pub-key :added])))))
|
||||
|
||||
(defn get-timeline-message
|
||||
[db chat-id message-js]
|
||||
(when (timeline-message? db chat-id)
|
||||
(data-store.messages/<-rpc (types/js->clj message-js))))
|
||||
|
||||
(defn add-message
|
||||
[{:keys [db] :as acc} message-js chat-id message-id cursor-clock-value]
|
||||
(let [{:keys [replace from clock-value] :as message}
|
||||
@ -148,30 +132,6 @@
|
||||
(when (seq senders)
|
||||
[{:ms 100 :dispatch [:chat/add-senders-to-chat-users (vals senders)]}]))}))
|
||||
|
||||
(defn reduce-js-statuses
|
||||
[db ^js message-js]
|
||||
(let [chat-id (.-localChatId message-js)
|
||||
profile-initialized (get-in db [:pagination-info chat-id :messages-initialized?])
|
||||
timeline-message (timeline-message? db chat-id)
|
||||
old-message (get-in db [:messages chat-id (.-id message-js)])]
|
||||
(if (and (or profile-initialized timeline-message) (nil? old-message))
|
||||
(let [{:keys [message-id] :as message} (data-store.messages/<-rpc (types/js->clj message-js))]
|
||||
(cond-> db
|
||||
profile-initialized
|
||||
(update-in [:messages chat-id] assoc message-id message)
|
||||
profile-initialized
|
||||
(update-in [:message-lists chat-id] message-list/add message)
|
||||
timeline-message
|
||||
(update-in [:messages constants/timeline-chat-id] assoc message-id message)
|
||||
timeline-message
|
||||
(update-in [:message-lists constants/timeline-chat-id] message-list/add message)))
|
||||
db)))
|
||||
|
||||
(rf/defn process-statuses
|
||||
{:events [:process-statuses]}
|
||||
[{:keys [db]} statuses]
|
||||
{:db (reduce reduce-js-statuses db statuses)})
|
||||
|
||||
(rf/defn update-db-message-status
|
||||
[{:keys [db] :as cofx} chat-id message-id status]
|
||||
(when (get-in db [:messages chat-id message-id])
|
||||
|
@ -1,16 +1,9 @@
|
||||
(ns status-im.contact.chat
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im.contact.core :as contact]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.navigation.events :as navigation]))
|
||||
|
||||
(rf/defn send-message-pressed
|
||||
{:events [:contact.ui/send-message-pressed]
|
||||
:interceptors [(re-frame/inject-cofx :random-id-generator)]}
|
||||
[cofx {:keys [public-key ens-name]}]
|
||||
(chat/start-chat cofx public-key ens-name))
|
||||
|
||||
(rf/defn contact-code-submitted
|
||||
{:events [:contact.ui/contact-code-submitted]
|
||||
:interceptors [(re-frame/inject-cofx :random-id-generator)]}
|
||||
@ -19,11 +12,6 @@
|
||||
(rf/merge cofx
|
||||
#(if new-contact?
|
||||
(contact/add-contact % public-key nickname ens-name)
|
||||
(chat/start-chat % public-key ens-name))
|
||||
{:dispatch [:chat.ui/start-chat public-key ens-name]})
|
||||
#(when new-contact?
|
||||
(navigation/navigate-back %)))))
|
||||
|
||||
(rf/defn pinned-messages-pressed
|
||||
{:events [:contact.ui/pinned-messages-pressed]}
|
||||
[cofx public-key]
|
||||
(chat/navigate-to-user-pinned-messages cofx public-key))
|
||||
|
@ -39,8 +39,6 @@
|
||||
blocked (:blocked contact)
|
||||
was-blocked (contact.db/blocked? db public-key)]
|
||||
(cond-> acc
|
||||
(and added (not was-added))
|
||||
(conj [:start-profile-chat public-key])
|
||||
|
||||
(and (not (:has-added-us contact))
|
||||
(= constants/contact-request-state-none (:contact-request-state contact)))
|
||||
@ -51,7 +49,7 @@
|
||||
|
||||
(and blocked (not was-blocked))
|
||||
(conj [::contact.block/contact-blocked contact chats]))))
|
||||
[[:offload-messages constants/timeline-chat-id]
|
||||
[[:chat/offload-messages constants/timeline-chat-id]
|
||||
[:activity-center.notifications/fetch-unread-count]]
|
||||
contacts)]
|
||||
(merge
|
||||
@ -93,7 +91,7 @@
|
||||
ens-name
|
||||
#(do
|
||||
(re-frame/dispatch [:sanitize-messages-and-process-response %])
|
||||
(re-frame/dispatch [:offload-messages constants/timeline-chat-id])))))
|
||||
(re-frame/dispatch [:chat/offload-messages constants/timeline-chat-id])))))
|
||||
|
||||
(rf/defn remove-contact
|
||||
"Remove a contact from current account's contact list"
|
||||
@ -109,7 +107,7 @@
|
||||
{:method "wakuext_retractContactRequest"
|
||||
:params [{:contactId public-key}]
|
||||
:on-success #(log/debug "contact removed successfully")}]
|
||||
:dispatch [:offload-messages constants/timeline-chat-id]})
|
||||
:dispatch [:chat/offload-messages constants/timeline-chat-id]})
|
||||
|
||||
(rf/defn accept-contact-request
|
||||
{:events [:contact-requests.ui/accept-request]}
|
||||
|
@ -8,7 +8,7 @@
|
||||
status-im.bootnodes.core
|
||||
status-im.browser.core
|
||||
status-im.browser.permissions
|
||||
[status-im.chat.models :as chat]
|
||||
status-im.chat.models
|
||||
status-im.chat.models.images
|
||||
status-im.chat.models.input
|
||||
status-im.chat.models.loading
|
||||
@ -184,12 +184,6 @@
|
||||
[{:keys [db]} dimensions]
|
||||
{:db (assoc db :dimensions/window (dimensions/window dimensions))})
|
||||
|
||||
(rf/defn init-timeline-chat
|
||||
{:events [:init-timeline-chat]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(when-not (get-in db [:pagination-info constants/timeline-chat-id :messages-initialized?])
|
||||
(chat/preload-chat-data cofx constants/timeline-chat-id)))
|
||||
|
||||
(rf/defn on-will-focus
|
||||
{:events [:screens/on-will-focus]}
|
||||
[{:keys [db] :as cofx} view-id]
|
||||
|
@ -5,7 +5,7 @@
|
||||
[utils.i18n :as i18n]
|
||||
[oops.core :as oops]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im2.contexts.chat.events :as chat.events]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.activity-center.events :as activity-center]
|
||||
[status-im2.navigation.events :as navigation]
|
||||
@ -15,7 +15,7 @@
|
||||
{:events [:navigate-chat-updated]}
|
||||
[cofx chat-id]
|
||||
(when (get-in cofx [:db :chats chat-id])
|
||||
(models.chat/navigate-to-chat-nav2 cofx chat-id nil)))
|
||||
(chat.events/navigate-to-chat cofx chat-id nil)))
|
||||
|
||||
(rf/defn handle-chat-removed
|
||||
{:events [:chat-removed]}
|
||||
@ -76,7 +76,7 @@
|
||||
(rf/defn create-from-link
|
||||
[cofx {:keys [chat-id invitation-admin chat-name]}]
|
||||
(if (get-in cofx [:db :chats chat-id])
|
||||
{:dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]}
|
||||
{:dispatch [:chat/navigate-to-chat chat-id]}
|
||||
{:json-rpc/call [{:method "wakuext_createGroupChatFromInvitation"
|
||||
:params [chat-name chat-id invitation-admin]
|
||||
:js-response true
|
||||
@ -123,7 +123,7 @@
|
||||
{:events [:group-chats.ui/remove-chat-confirmed]}
|
||||
[cofx chat-id]
|
||||
(rf/merge cofx
|
||||
(models.chat/deactivate-chat chat-id)
|
||||
(chat.events/deactivate-chat chat-id)
|
||||
(navigation/pop-to-root-tab :chat-stack)))
|
||||
|
||||
(def not-blank?
|
||||
|
@ -3,9 +3,9 @@
|
||||
[clojure.string :as string]
|
||||
[day8.re-frame.test :as rf-test]
|
||||
[re-frame.core :as rf]
|
||||
[status-im.chat.models :as chat.models]
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
status-im.events
|
||||
status-im2.events
|
||||
[status-im.multiaccounts.logout.core :as logout]
|
||||
[status-im.transport.core :as transport]
|
||||
[status-im.utils.test :as utils.test]
|
||||
@ -250,8 +250,8 @@
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[:status-im.chat.models/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat.ui/navigate-to-chat-nav2 chat-id])
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not in
|
||||
@ -275,20 +275,17 @@
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[:status-im.chat.models/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat.ui/navigate-to-chat-nav2 chat-id])
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(is @(rf/subscribe [:chats/chat chat-id]))
|
||||
(rf/dispatch-sync [:chat.ui/remove-chat-pressed chat-id])
|
||||
(rf/dispatch-sync [:chat.ui/show-remove-confirmation chat-id])
|
||||
(rf/dispatch-sync [:chat.ui/remove-chat chat-id])
|
||||
(rf-test/wait-for
|
||||
[::chat.models/chat-deactivated]
|
||||
(is (not @(rf/subscribe [:chats/chat chat-id])))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not
|
||||
; in an
|
||||
; inconsistent state between tests
|
||||
(assert-logout)))))))))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not
|
||||
; in an
|
||||
; inconsistent state between tests
|
||||
(assert-logout))))))))
|
||||
|
||||
(deftest mute-chat-test
|
||||
(log/info "========= mute-chat-test ==================")
|
||||
@ -306,18 +303,17 @@
|
||||
(assert-messenger-started)
|
||||
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
|
||||
(rf-test/wait-for
|
||||
[:status-im.chat.models/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat.ui/navigate-to-chat-nav2 chat-id])
|
||||
[:chat/one-to-one-chat-created]
|
||||
(rf/dispatch-sync [:chat/navigate-to-chat chat-id])
|
||||
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
|
||||
(is @(rf/subscribe [:chats/chat chat-id]))
|
||||
(rf/dispatch-sync [::chat.models/mute-chat-toggled chat-id true])
|
||||
(rf/dispatch-sync [:chat.ui/mute chat-id true])
|
||||
(rf-test/wait-for
|
||||
[::chat.models/mute-chat-toggled-successfully]
|
||||
[:chat/mute-successfully]
|
||||
(is @(rf/subscribe [:chats/muted chat-id]))
|
||||
(rf/dispatch-sync [::chat.models/mute-chat-toggled chat-id false])
|
||||
(rf/dispatch-sync [:chat.ui/mute chat-id false])
|
||||
(rf-test/wait-for
|
||||
[::chat.models/mute-chat-toggled-successfully]
|
||||
|
||||
[:chat/mute-successfully]
|
||||
(is (not @(rf/subscribe [:chats/muted chat-id])))
|
||||
(logout!)
|
||||
(rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is
|
||||
|
@ -424,7 +424,7 @@
|
||||
:key-uid
|
||||
(fn [stored-key-uid]
|
||||
(when (= stored-key-uid key-uid)
|
||||
(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])))))))))
|
||||
(re-frame/dispatch [:chat/navigate-to-chat chat-id])))))))))
|
||||
|
||||
(rf/defn check-last-chat
|
||||
{:events [::check-last-chat]}
|
||||
|
@ -303,7 +303,7 @@
|
||||
(re-frame/dispatch [:set :current-tab tab-key])
|
||||
(when (= @state/root-comp-id comp)
|
||||
(when (= :chat tab-key)
|
||||
(re-frame/dispatch [:close-chat]))
|
||||
(re-frame/dispatch [:chat/close]))
|
||||
(when platform/android?
|
||||
(.popToRoot Navigation (name comp))))
|
||||
(reset! state/root-comp-id comp)))))
|
||||
|
@ -5,7 +5,6 @@
|
||||
[quo.platform :as platform]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.async-storage.core :as async-storage]
|
||||
[status-im.chat.models :as chat.models]
|
||||
[status-im.ethereum.decode :as decode]
|
||||
[status-im.ethereum.tokens :as tokens]
|
||||
[utils.i18n :as i18n]
|
||||
@ -105,6 +104,11 @@
|
||||
:user-info notification
|
||||
:message description}))
|
||||
|
||||
(defn foreground-chat?
|
||||
[{{:keys [current-chat-id view-id]} :db} chat-id]
|
||||
(and (= current-chat-id chat-id)
|
||||
(= view-id :chat)))
|
||||
|
||||
(defn show-message-pn?
|
||||
[{{:keys [app-state multiaccount]} :db :as cofx}
|
||||
notification]
|
||||
@ -113,7 +117,7 @@
|
||||
(and
|
||||
(not= notification-author (:public-key multiaccount))
|
||||
(or (= app-state "background")
|
||||
(not (chat.models/foreground-chat? cofx chat-id))))))
|
||||
(not (foreground-chat? cofx chat-id))))))
|
||||
|
||||
(defn create-notification
|
||||
([notification]
|
||||
|
@ -84,3 +84,12 @@
|
||||
{:events [:profile/share-profile-link]}
|
||||
[_ value]
|
||||
{:profile/share-profile-link value})
|
||||
|
||||
(rf/defn show-profile
|
||||
{:events [:chat.ui/show-profile]}
|
||||
[{:keys [db]} identity ens-name]
|
||||
(let [my-public-key (get-in db [:multiaccount :public-key])]
|
||||
(when (not= my-public-key identity)
|
||||
{:db (-> db
|
||||
(assoc :contacts/identity identity)
|
||||
(assoc :contacts/ens-name ens-name))})))
|
||||
|
@ -47,7 +47,7 @@
|
||||
(rf/defn handle-private-chat
|
||||
[{:keys [db] :as cofx} {:keys [chat-id]}]
|
||||
(if-not (new-chat.db/own-public-key? db chat-id)
|
||||
(chat/start-chat cofx chat-id nil)
|
||||
{:dispatch [:chat.ui/start-chat chat-id]}
|
||||
{:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code)
|
||||
:content (i18n/label :t/can-not-add-yourself)}}))
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
[clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.add-new.db :as public-chat.db]
|
||||
[status-im.chat.models :as chat.models]
|
||||
[status-im2.contexts.chat.events :as chat.events]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
[status-im.ethereum.eip681 :as eip681]
|
||||
@ -121,7 +121,7 @@
|
||||
:chat-name chat-name}
|
||||
|
||||
(and (not (string/blank? chat-id))
|
||||
(chat.models/group-chat? (get chats chat-id)))
|
||||
(chat.events/group-chat? (get chats chat-id)))
|
||||
(let [{:keys [chat-name invitation-admin]} (get chats chat-id)]
|
||||
{:type :group-chat
|
||||
:chat-id chat-id
|
||||
|
@ -2,7 +2,7 @@
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[status-im.browser.core :as browser]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im2.contexts.chat.events :as chat.events]
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.chat.models.reactions :as models.reactions]
|
||||
[status-im.communities.core :as models.communities]
|
||||
@ -61,7 +61,7 @@
|
||||
(js-delete response-js "chats")
|
||||
(rf/merge cofx
|
||||
(process-next response-js sync-handler)
|
||||
(models.chat/ensure-chats (map data-store.chats/<-rpc (types/js->clj chats)))))
|
||||
(chat.events/ensure-chats (map data-store.chats/<-rpc (types/js->clj chats)))))
|
||||
|
||||
(seq messages)
|
||||
(models.message/receive-many cofx response-js)
|
||||
@ -200,12 +200,10 @@
|
||||
message-type (.-messageType message-js)
|
||||
from (.-from message-js)
|
||||
mentioned (.-mentioned message-js)
|
||||
profile (models.chat/profile-chat? {:db db} chat-id)
|
||||
new (.-new message-js)
|
||||
current (= current-chat-id chat-id)
|
||||
should-update-unviewed? (and (not current)
|
||||
new
|
||||
(not profile)
|
||||
(not (= message-type
|
||||
constants/message-type-private-group-system-message))
|
||||
(not (= from (multiaccounts.model/current-public-key {:db db}))))
|
||||
@ -215,9 +213,6 @@
|
||||
current
|
||||
(update :messages conj message-js)
|
||||
|
||||
profile
|
||||
(update :statuses conj message-js)
|
||||
|
||||
;;update counter
|
||||
should-update-unviewed?
|
||||
(update-in [:db :chats chat-id :unviewed-messages-count] inc)
|
||||
|
@ -7,11 +7,6 @@
|
||||
[status-im.ui.screens.chat.styles.input.gap :as style]
|
||||
[utils.datetime :as datetime]))
|
||||
|
||||
(defn on-press
|
||||
[chat-id gap-ids]
|
||||
(fn []
|
||||
(re-frame/dispatch [:chat.ui/fill-gaps chat-id gap-ids])))
|
||||
|
||||
(views/defview gap
|
||||
[{:keys [gap-ids chat-id gap-parameters public? community?]}]
|
||||
(views/letsubs [in-progress? [:chats/fetching-gap-in-progress?
|
||||
@ -25,7 +20,7 @@
|
||||
[react/view {:style (when-not in-progress? style/gap-container)}
|
||||
[react/touchable-highlight
|
||||
{:on-press (when (and (not in-progress?) use-status-nodes? connected?)
|
||||
(on-press chat-id gap-ids))
|
||||
(re-frame/dispatch [:chat.ui/fill-gaps chat-id gap-ids]))
|
||||
:style {:height (if in-progress? window-height 48)}}
|
||||
[react/view {:style style/label-container}
|
||||
(if in-progress?
|
||||
|
@ -40,7 +40,7 @@
|
||||
:title (i18n/label :t/delete-chat)
|
||||
:accessibility-label :delete-chat-button
|
||||
:icon :main-icons/delete
|
||||
:on-press #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])}]]))
|
||||
:on-press #(re-frame/dispatch [:chat.ui/show-remove-confirmation chat-id])}]]))
|
||||
|
||||
(defn public-chat-accents
|
||||
[chat-id]
|
||||
@ -69,13 +69,13 @@
|
||||
:title (i18n/label :t/clear-history)
|
||||
:accessibility-label :clear-history-button
|
||||
:icon :main-icons/close
|
||||
:on-press #(re-frame/dispatch [:chat.ui/clear-history-pressed chat-id])}]
|
||||
:on-press #(re-frame/dispatch [:chat.ui/show-clear-history-confirmation chat-id])}]
|
||||
[quo/list-item
|
||||
{:theme :negative
|
||||
:title (i18n/label :t/delete-chat)
|
||||
:accessibility-label :delete-chat-button
|
||||
:icon :main-icons/delete
|
||||
:on-press #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])}]]))
|
||||
:on-press #(re-frame/dispatch [:chat.ui/show-remove-confirmation chat-id])}]]))
|
||||
|
||||
(defn community-chat-accents
|
||||
[]
|
||||
|
@ -64,4 +64,5 @@
|
||||
:accessory :text
|
||||
:accessory-text (count pinned-messages)
|
||||
:chevron true
|
||||
:on-press #(re-frame/dispatch [:contact.ui/pinned-messages-pressed chat-id])}]])]))))
|
||||
:on-press #(re-frame/dispatch [:chat.ui/navigate-to-pinned-messages
|
||||
chat-id])}]])]))))
|
||||
|
@ -171,7 +171,7 @@
|
||||
(assoc home-item :public? true)
|
||||
{:on-press (fn []
|
||||
(rf/dispatch [:dismiss-keyboard])
|
||||
(rf/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
|
||||
(rf/dispatch [:chat/navigate-to-chat chat-id])
|
||||
(rf/dispatch [:search/home-filter-changed nil]))
|
||||
:on-long-press #(rf/dispatch [:bottom-sheet/show-sheet
|
||||
{:content (fn []
|
||||
|
@ -151,7 +151,7 @@
|
||||
home-item
|
||||
{:on-press (fn []
|
||||
(re-frame/dispatch [:dismiss-keyboard])
|
||||
(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
|
||||
(re-frame/dispatch [:chat/navigate-to-chat chat-id])
|
||||
(re-frame/dispatch [:search/home-filter-changed nil]))
|
||||
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
|
||||
{:content (fn []
|
||||
@ -166,7 +166,7 @@
|
||||
home-item
|
||||
{:on-press (fn []
|
||||
(re-frame/dispatch [:dismiss-keyboard])
|
||||
(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
|
||||
(re-frame/dispatch [:chat/navigate-to-chat chat-id])
|
||||
(re-frame/dispatch [:search/home-filter-changed nil]))
|
||||
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
|
||||
{:content (fn []
|
||||
@ -193,7 +193,7 @@
|
||||
[list/flat-list
|
||||
{:key-fn chat-list-key-fn
|
||||
:getItemLayout get-item-layout
|
||||
:on-end-reached #(re-frame/dispatch [:chat.ui/show-more-chats])
|
||||
:on-end-reached #(re-frame/dispatch [:chat/show-more-chats])
|
||||
:keyboard-should-persist-taps :always
|
||||
:data items
|
||||
:render-fn render-fn
|
||||
@ -219,7 +219,7 @@
|
||||
[list/flat-list
|
||||
{:key-fn chat-list-key-fn
|
||||
:getItemLayout get-item-layout
|
||||
:on-end-reached #(re-frame/dispatch [:chat.ui/show-more-chats])
|
||||
:on-end-reached #(re-frame/dispatch [:chat/show-more-chats])
|
||||
:keyboard-should-persist-taps :always
|
||||
:data items
|
||||
:render-fn render-fn-old
|
||||
|
@ -5,7 +5,6 @@
|
||||
[quo.design-system.colors :as colors]
|
||||
[re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.chat.models :as chat.models]
|
||||
[utils.i18n :as i18n]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.ui.components.chat-icon.screen :as chat-icon]
|
||||
@ -25,7 +24,7 @@
|
||||
(concat [{:label (i18n/label :t/chat)
|
||||
:icon :main-icons/message
|
||||
:disabled (not mutual?)
|
||||
:action #(re-frame/dispatch [:contact.ui/send-message-pressed
|
||||
:action #(re-frame/dispatch [:chat.ui/start-chat
|
||||
{:public-key public-key
|
||||
:ens-name ens-name}])
|
||||
:accessibility-label :start-conversation-button}]
|
||||
@ -48,8 +47,7 @@
|
||||
:selected muted?
|
||||
:disabled blocked?
|
||||
:action (when-not blocked?
|
||||
#(re-frame/dispatch [::chat.models/mute-chat-toggled public-key
|
||||
(not muted?)]))}]
|
||||
#(re-frame/dispatch [:chat.ui/mute public-key (not muted?)]))}]
|
||||
[{:label (i18n/label (if blocked? :t/unblock :t/block))
|
||||
:negative true
|
||||
:selected blocked?
|
||||
@ -102,7 +100,7 @@
|
||||
:accessory :text
|
||||
:accessory-text pin-count
|
||||
:disabled (zero? pin-count)
|
||||
:on-press #(re-frame/dispatch [:contact.ui/pinned-messages-pressed public-key])
|
||||
:on-press #(re-frame/dispatch [:chat.ui/navigate-to-pinned-messages public-key])
|
||||
:chevron true}])
|
||||
|
||||
(defn nickname-settings
|
||||
|
@ -215,7 +215,7 @@
|
||||
:accessory :text
|
||||
:accessory-text (count pinned-messages)
|
||||
:chevron true
|
||||
:on-press #(re-frame/dispatch [:contact.ui/pinned-messages-pressed chat-id])}]
|
||||
:on-press #(re-frame/dispatch [:chat.ui/navigate-to-pinned-messages chat-id])}]
|
||||
(when member?
|
||||
[quo/list-item
|
||||
{:theme :negative
|
||||
|
@ -65,7 +65,7 @@
|
||||
(log/info "universal-links: handling private chat" chat-id)
|
||||
(when chat-id
|
||||
(if-not (new-chat.db/own-public-key? db chat-id)
|
||||
(chat/start-chat cofx chat-id nil)
|
||||
{:dispatch [:chat.ui/start-chat chat-id]}
|
||||
{:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code)
|
||||
:content (i18n/label :t/can-not-add-yourself)}})))
|
||||
|
||||
@ -96,7 +96,7 @@
|
||||
(rf/defn handle-community-chat
|
||||
[cofx {:keys [chat-id]}]
|
||||
(log/info "universal-links: handling community chat" chat-id)
|
||||
{:dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]})
|
||||
{:dispatch [:chat/navigate-to-chat chat-id]})
|
||||
|
||||
(rf/defn handle-public-chat
|
||||
[cofx {:keys [topic]}]
|
||||
|
@ -1,9 +1,7 @@
|
||||
(ns status-im2.common.home.actions.view
|
||||
(:require [utils.i18n :as i18n]
|
||||
[quo2.components.drawers.action-drawers :as drawer]
|
||||
[status-im.chat.models :as chat.models]
|
||||
[status-im2.common.confirmation-drawer.view :as confirmation-drawer] ;;TODO move to
|
||||
;;status-im2
|
||||
[status-im2.common.confirmation-drawer.view :as confirmation-drawer]
|
||||
[status-im2.constants :as constants]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
@ -43,11 +41,11 @@
|
||||
|
||||
(defn mute-chat-action
|
||||
[chat-id]
|
||||
(hide-sheet-and-dispatch [::chat.models/mute-chat-toggled chat-id true]))
|
||||
(hide-sheet-and-dispatch [:chat.ui/mute chat-id true]))
|
||||
|
||||
(defn unmute-chat-action
|
||||
[chat-id]
|
||||
(hide-sheet-and-dispatch [::chat.models/mute-chat-toggled chat-id false]))
|
||||
(hide-sheet-and-dispatch [:chat.ui/mute chat-id false]))
|
||||
|
||||
(defn clear-history-action
|
||||
[{:keys [chat-id] :as item}]
|
||||
|
@ -13,14 +13,14 @@
|
||||
pressable (case (:contact-request-state message)
|
||||
constants/contact-request-message-state-accepted
|
||||
;; NOTE(2022-09-21): We need to dispatch to
|
||||
;; `:contact.ui/send-message-pressed` instead of
|
||||
;; `:chat.ui/navigate-to-chat`, otherwise the chat screen
|
||||
;; `:chat.ui/start-chat` instead of
|
||||
;; `:chat/navigate-to-chat`, otherwise the chat screen
|
||||
;; looks completely broken if it has never been opened
|
||||
;; before for the accepted contact.
|
||||
[rn/touchable-opacity
|
||||
{:on-press (fn []
|
||||
(rf/dispatch [:hide-popover])
|
||||
(rf/dispatch [:contact.ui/send-message-pressed
|
||||
(rf/dispatch [:chat.ui/start-chat
|
||||
{:public-key author}]))}]
|
||||
[:<>])]
|
||||
(conj
|
||||
|
@ -46,7 +46,7 @@
|
||||
[rn/touchable-opacity
|
||||
{:on-press (fn []
|
||||
(rf/dispatch [:hide-popover])
|
||||
(rf/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]))}
|
||||
(rf/dispatch [:chat/navigate-to-chat chat-id]))}
|
||||
[quo/activity-log
|
||||
{:title (i18n/label :t/mention)
|
||||
:icon :i/mention
|
||||
|
@ -38,7 +38,7 @@
|
||||
[rn/touchable-opacity
|
||||
{:on-press (fn []
|
||||
(rf/dispatch [:hide-popover])
|
||||
(rf/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]))}
|
||||
(rf/dispatch [:chat/navigate-to-chat chat-id]))}
|
||||
[quo/activity-log
|
||||
{:title (i18n/label :t/message-reply)
|
||||
:icon :i/reply
|
||||
|
328
src/status_im2/contexts/chat/events.cljs
Normal file
328
src/status_im2/contexts/chat/events.cljs
Normal file
@ -0,0 +1,328 @@
|
||||
(ns status-im2.contexts.chat.events
|
||||
(:require [clojure.set :as set]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im2.contexts.chat.messages.list.state :as chat.state]
|
||||
[status-im2.contexts.chat.messages.delete-message-for-me.events :as delete-for-me]
|
||||
[status-im2.contexts.chat.messages.delete-message.events :as delete-message]
|
||||
[status-im2.navigation.events :as navigation]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im.chat.models.loading :as loading]
|
||||
[status-im.data-store.chats :as chats-store]
|
||||
[status-im.data-store.contacts :as contacts-store]
|
||||
[status-im.multiaccounts.model :as multiaccounts.model]
|
||||
[status-im.utils.clocks :as utils.clocks]))
|
||||
|
||||
(defn- get-chat
|
||||
[cofx chat-id]
|
||||
(get-in cofx [:db :chats chat-id]))
|
||||
|
||||
(defn multi-user-chat?
|
||||
([chat]
|
||||
(:group-chat chat))
|
||||
([cofx chat-id]
|
||||
(multi-user-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn public-chat?
|
||||
([chat]
|
||||
(:public? chat))
|
||||
([cofx chat-id]
|
||||
(public-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn community-chat?
|
||||
([{:keys [chat-type]}]
|
||||
(= chat-type constants/community-chat-type))
|
||||
([cofx chat-id]
|
||||
(community-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn active-chat?
|
||||
[cofx chat-id]
|
||||
(let [chat (get-chat cofx chat-id)]
|
||||
(:active chat)))
|
||||
|
||||
(defn group-chat?
|
||||
([chat]
|
||||
(and (multi-user-chat? chat)
|
||||
(not (public-chat? chat))))
|
||||
([cofx chat-id]
|
||||
(group-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn- create-new-chat
|
||||
[chat-id {:keys [db now]}]
|
||||
(let [name (get-in db [:contacts/contacts chat-id :name])]
|
||||
{:chat-id chat-id
|
||||
:name (or name "")
|
||||
:chat-type constants/one-to-one-chat-type
|
||||
:group-chat false
|
||||
:timestamp now
|
||||
:contacts #{chat-id}
|
||||
:last-clock-value 0}))
|
||||
|
||||
(defn map-chats
|
||||
[{:keys [db] :as cofx}]
|
||||
(fn [val]
|
||||
(assoc
|
||||
(merge
|
||||
(or (get (:chats db) (:chat-id val))
|
||||
(create-new-chat (:chat-id val) cofx))
|
||||
val)
|
||||
:invitation-admin
|
||||
(:invitation-admin val))))
|
||||
|
||||
(rf/defn leave-removed-chat
|
||||
[{{:keys [view-id current-chat-id chats]} :db
|
||||
:as cofx}]
|
||||
(when (and (= view-id :chat)
|
||||
(not (contains? chats current-chat-id)))
|
||||
(navigation/navigate-back cofx)))
|
||||
|
||||
(rf/defn ensure-chats
|
||||
"Add chats to db and update"
|
||||
[{:keys [db] :as cofx} chats]
|
||||
(let [{:keys [all-chats chats-home-list removed-chats]}
|
||||
(reduce
|
||||
(fn [acc {:keys [chat-id profile-public-key timeline? community-id active muted] :as chat}]
|
||||
(if (not (or active muted))
|
||||
(update acc :removed-chats conj chat-id)
|
||||
(cond-> acc
|
||||
(and (not profile-public-key) (not timeline?) (not community-id) active)
|
||||
(update :chats-home-list conj chat-id)
|
||||
:always
|
||||
(assoc-in [:all-chats chat-id] chat))))
|
||||
{:all-chats {}
|
||||
:chats-home-list #{}
|
||||
:removed-chats #{}}
|
||||
(map (map-chats cofx) chats))]
|
||||
(rf/merge
|
||||
cofx
|
||||
(merge {:db (-> db
|
||||
(update :chats merge all-chats)
|
||||
(update :chats-home-list set/union chats-home-list)
|
||||
(update :chats #(apply dissoc % removed-chats))
|
||||
(update :chats-home-list set/difference removed-chats))}
|
||||
(when (not-empty removed-chats)
|
||||
{:clear-message-notifications
|
||||
[removed-chats
|
||||
(get-in db [:multiaccount :remote-push-notifications-enabled?])]}))
|
||||
leave-removed-chat)))
|
||||
|
||||
(rf/defn clear-history
|
||||
"Clears history of the particular chat"
|
||||
[{:keys [db]} chat-id remove-chat?]
|
||||
(let [{:keys [last-message public?
|
||||
deleted-at-clock-value]}
|
||||
(get-in db [:chats chat-id])
|
||||
last-message-clock-value (if (and public? remove-chat?)
|
||||
0
|
||||
(or (:clock-value last-message)
|
||||
deleted-at-clock-value
|
||||
(utils.clocks/send 0)))]
|
||||
{:db (-> db
|
||||
(assoc-in [:messages chat-id] {})
|
||||
(update-in [:message-lists] dissoc chat-id)
|
||||
(update :chats
|
||||
(fn [chats]
|
||||
(if (contains? chats chat-id)
|
||||
(update chats
|
||||
chat-id
|
||||
merge
|
||||
{:last-message nil
|
||||
:unviewed-messages-count 0
|
||||
:unviewed-mentions-count 0
|
||||
:deleted-at-clock-value last-message-clock-value})
|
||||
chats))))}))
|
||||
|
||||
(rf/defn deactivate-chat
|
||||
"Deactivate chat in db, no side effects"
|
||||
[{:keys [db now] :as cofx} chat-id]
|
||||
(rf/merge
|
||||
cofx
|
||||
{:db (-> (if (get-in db [:chats chat-id :muted])
|
||||
(assoc-in db [:chats chat-id :active] false)
|
||||
(update db :chats dissoc chat-id))
|
||||
(update :chats-home-list disj chat-id)
|
||||
(assoc :current-chat-id nil))
|
||||
:json-rpc/call [{:method "wakuext_deactivateChat"
|
||||
:params [{:id chat-id}]
|
||||
:on-success #()
|
||||
:on-error #(log/error "failed to create public chat" chat-id %)}]}
|
||||
(clear-history chat-id true)))
|
||||
|
||||
(rf/defn offload-messages
|
||||
{:events [:chat/offload-messages]}
|
||||
[{:keys [db]} chat-id]
|
||||
{:db (-> db
|
||||
(update :messages dissoc chat-id)
|
||||
(update :message-lists dissoc chat-id)
|
||||
(update :pagination-info dissoc chat-id))})
|
||||
|
||||
(rf/defn close-chat
|
||||
{:events [:chat/close]}
|
||||
[{:keys [db] :as cofx} navigate-to-shell?]
|
||||
(when-let [chat-id (:current-chat-id db)]
|
||||
(chat.state/reset-visible-item)
|
||||
(rf/merge cofx
|
||||
(merge
|
||||
{:db (dissoc db :current-chat-id)}
|
||||
(let [community-id (get-in db [:chats chat-id :community-id])]
|
||||
;; When navigating back from community chat to community, update switcher card
|
||||
(when (and community-id (not navigate-to-shell?))
|
||||
{:dispatch [:shell/add-switcher-card
|
||||
:community {:community-id community-id}]})))
|
||||
(delete-for-me/sync-all)
|
||||
(delete-message/send-all)
|
||||
(offload-messages chat-id))))
|
||||
|
||||
(rf/defn force-close-chat
|
||||
[{:keys [db] :as cofx} chat-id]
|
||||
(do
|
||||
(chat.state/reset-visible-item)
|
||||
(rf/merge cofx
|
||||
{:db (dissoc db :current-chat-id)}
|
||||
(offload-messages chat-id))))
|
||||
|
||||
(rf/defn show-more-chats
|
||||
{:events [:chat/show-more-chats]}
|
||||
[{:keys [db]}]
|
||||
(when (< (:home-items-show-number db) (count (:chats db)))
|
||||
{:db (update db :home-items-show-number + 40)}))
|
||||
|
||||
(rf/defn preload-chat-data
|
||||
"Takes chat-id and coeffects map, returns effects necessary when navigating to chat"
|
||||
{:events [:chat/preload-data]}
|
||||
[cofx chat-id]
|
||||
(loading/load-messages cofx chat-id))
|
||||
|
||||
(rf/defn navigate-to-chat
|
||||
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
|
||||
{:events [:chat/navigate-to-chat]}
|
||||
[{db :db :as cofx} chat-id from-shell?]
|
||||
(rf/merge cofx
|
||||
{:dispatch [:navigate-to-nav2 :chat chat-id from-shell?]}
|
||||
(when-not (= (:view-id db) :community)
|
||||
(navigation/pop-to-root-tab :shell-stack))
|
||||
(close-chat false)
|
||||
(force-close-chat chat-id)
|
||||
(fn [{:keys [db]}]
|
||||
{:db (assoc db :current-chat-id chat-id)})
|
||||
(preload-chat-data chat-id)
|
||||
#(when (group-chat? cofx chat-id)
|
||||
(loading/load-chat % chat-id))))
|
||||
|
||||
(rf/defn handle-clear-history-response
|
||||
{:events [:chat/history-cleared]}
|
||||
[{:keys [db]} chat-id response]
|
||||
(let [chat (chats-store/<-rpc (first (:chats response)))]
|
||||
{:db (assoc-in db [:chats chat-id] chat)}))
|
||||
|
||||
(rf/defn handle-one-to-one-chat-created
|
||||
{:events [:chat/one-to-one-chat-created]}
|
||||
[{:keys [db]} chat-id response]
|
||||
(let [chat (chats-store/<-rpc (first (:chats response)))
|
||||
contact-rpc (first (:contacts response))
|
||||
contact (when contact-rpc (contacts-store/<-rpc contact-rpc))]
|
||||
{:db (cond-> db
|
||||
contact
|
||||
(assoc-in [:contacts/contacts chat-id] contact)
|
||||
:always
|
||||
(assoc-in [:chats chat-id] chat)
|
||||
:always
|
||||
(update :chats-home-list conj chat-id))
|
||||
:dispatch [:chat/navigate-to-chat chat-id]}))
|
||||
|
||||
(rf/defn decrease-unviewed-count
|
||||
{:events [:chat/decrease-unviewed-count]}
|
||||
[{:keys [db]} chat-id {:keys [count countWithMentions]}]
|
||||
{:db (-> db
|
||||
;; There might be some other requests being fired,
|
||||
;; so we need to make sure the count has not been set to
|
||||
;; 0 in the meantime
|
||||
(update-in [:chats chat-id :unviewed-messages-count]
|
||||
#(max (- % count) 0))
|
||||
(update-in [:chats chat-id :unviewed-mentions-count]
|
||||
#(max (- % countWithMentions) 0)))})
|
||||
|
||||
;;;; UI
|
||||
|
||||
(rf/defn start-chat
|
||||
"Start a chat, making sure it exists"
|
||||
{:events [:chat.ui/start-chat]}
|
||||
[cofx chat-id ens-name]
|
||||
(when (not= (multiaccounts.model/current-public-key cofx) chat-id)
|
||||
{:json-rpc/call [{:method "wakuext_createOneToOneChat"
|
||||
:params [{:id chat-id :ensName ens-name}]
|
||||
:on-success #(rf/dispatch [:chat/one-to-one-chat-created chat-id %])
|
||||
:on-error #(log/error "failed to create one-to-on chat" chat-id %)}]}))
|
||||
|
||||
(rf/defn clear-history-handler
|
||||
"Clears history of the particular chat"
|
||||
{:events [:chat.ui/clear-history]}
|
||||
[{:keys [db] :as cofx} chat-id remove-chat?]
|
||||
(rf/merge cofx
|
||||
{:db db
|
||||
:json-rpc/call [{:method "wakuext_clearHistory"
|
||||
:params [{:id chat-id}]
|
||||
:on-success #(rf/dispatch [:chat/history-cleared chat-id %])
|
||||
:on-error #(log/error "failed to clear history " chat-id %)}]}
|
||||
(clear-history chat-id remove-chat?)))
|
||||
|
||||
(rf/defn remove-chat
|
||||
"Removes chat completely from app, producing all necessary effects for that"
|
||||
{:events [:chat.ui/remove-chat]}
|
||||
[{:keys [db now] :as cofx} chat-id]
|
||||
(rf/merge cofx
|
||||
{:clear-message-notifications
|
||||
[[chat-id] (get-in db [:multiaccount :remote-push-notifications-enabled?])]
|
||||
:dispatch [:shell/close-switcher-card chat-id]}
|
||||
(deactivate-chat chat-id)
|
||||
(offload-messages chat-id)))
|
||||
|
||||
(rf/defn mute-chat-failed
|
||||
{:events [:chat/mute-failed]}
|
||||
[{:keys [db]} chat-id muted? error]
|
||||
(log/error "mute chat failed" chat-id error)
|
||||
{:db (assoc-in db [:chats chat-id :muted] (not muted?))})
|
||||
|
||||
(rf/defn mute-chat-toggled-successfully
|
||||
{:events [:chat/mute-successfully]}
|
||||
[_ chat-id]
|
||||
(log/debug "muted chat successfully" chat-id))
|
||||
|
||||
(rf/defn mute-chat
|
||||
{:events [:chat.ui/mute]}
|
||||
[{:keys [db]} chat-id muted?]
|
||||
(let [method (if muted? "wakuext_muteChat" "wakuext_unmuteChat")]
|
||||
{:db (assoc-in db [:chats chat-id :muted] muted?)
|
||||
:json-rpc/call [{:method method
|
||||
:params [chat-id]
|
||||
:on-error #(rf/dispatch [:chat/mute-failed chat-id muted? %])
|
||||
:on-success #(rf/dispatch [:chat/mute-successfully chat-id])}]}))
|
||||
|
||||
(rf/defn show-clear-history-confirmation
|
||||
{:events [:chat.ui/show-clear-history-confirmation]}
|
||||
[_ chat-id]
|
||||
{:ui/show-confirmation
|
||||
{:title (i18n/label :t/clear-history-title)
|
||||
:content (i18n/label :t/clear-history-confirmation-content)
|
||||
:confirm-button-text (i18n/label :t/clear-history-action)
|
||||
:on-accept #(do
|
||||
(rf/dispatch [:bottom-sheet/hide])
|
||||
(rf/dispatch [:chat.ui/clear-history chat-id false]))}})
|
||||
|
||||
(rf/defn show-remove-chat-confirmation
|
||||
{:events [:chat.ui/show-remove-confirmation]}
|
||||
[_ chat-id]
|
||||
{:ui/show-confirmation
|
||||
{:title (i18n/label :t/delete-confirmation)
|
||||
:content (i18n/label :t/delete-chat-confirmation)
|
||||
:confirm-button-text (i18n/label :t/delete)
|
||||
:on-accept #(do
|
||||
(rf/dispatch [:bottom-sheet/hide])
|
||||
(rf/dispatch [:chat.ui/remove-chat chat-id]))}})
|
||||
|
||||
(rf/defn navigate-to-user-pinned-messages
|
||||
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
|
||||
{:events [:chat.ui/navigate-to-pinned-messages]}
|
||||
[cofx chat-id]
|
||||
(navigation/navigate-to cofx :chat-pinned-messages {:chat-id chat-id}))
|
@ -1,6 +1,6 @@
|
||||
(ns status-im.chat.models-test
|
||||
(ns status-im2.contexts.chat.events-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im2.contexts.chat.events :as chat]
|
||||
[status-im.chat.models.images :as images]
|
||||
[status-im.utils.clocks :as utils.clocks]))
|
||||
|
||||
@ -49,10 +49,7 @@
|
||||
:chats {chat-id {:last-message {:clock-value 10}}}}}]
|
||||
(testing "it deletes all the messages"
|
||||
(let [actual (chat/remove-chat cofx chat-id)]
|
||||
(is (= nil (get-in actual [:db :messages chat-id])))))
|
||||
#_(testing "it sets a deleted-at-clock-value equal to the last message clock-value"
|
||||
(let [actual (chat/remove-chat cofx chat-id)]
|
||||
(is (= 10 (get-in actual [:db :chats chat-id :deleted-at-clock-value])))))))
|
||||
(is (= nil (get-in actual [:db :messages chat-id])))))))
|
||||
|
||||
(deftest multi-user-chat?
|
||||
(let [chat-id "1"]
|
||||
@ -87,11 +84,11 @@
|
||||
"opened" {}
|
||||
"1-1" {}}})
|
||||
|
||||
(deftest navigate-to-chat-nav2
|
||||
(deftest navigate-to-chat
|
||||
(let [chat-id "test_chat"
|
||||
db {:pagination-info {chat-id {:all-loaded? true}}}]
|
||||
(testing "Pagination info should be reset on navigation"
|
||||
(let [res (chat/navigate-to-chat-nav2 {:db db} chat-id false)]
|
||||
(let [res (chat/navigate-to-chat {:db db} chat-id false)]
|
||||
(is (nil? (get-in res [:db :pagination-info chat-id :all-loaded?])))))))
|
||||
|
||||
(deftest camera-roll-loading-more-test
|
@ -4,7 +4,6 @@
|
||||
[quo2.core :as quo2]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[status-im.chat.models :as chat.models]
|
||||
[status-im2.contexts.chat.group-details.style :as style]
|
||||
[status-im2.common.contact-list.view :as contact-list]
|
||||
[status-im2.common.contact-list-item.view :as contact-list-item]
|
||||
@ -147,7 +146,7 @@
|
||||
[rn/touchable-opacity
|
||||
{:style (style/action-container color)
|
||||
:accessibility-label :toggle-mute
|
||||
:on-press #(rf/dispatch [::chat.models/mute-chat-toggled chat-id (not muted)])}
|
||||
:on-press #(rf/dispatch [:chat.ui/mute chat-id (not muted)])}
|
||||
[quo2/icon (if muted :i/muted :i/activity-center)
|
||||
{:size 20 :color (colors/theme-colors colors/neutral-100 colors/white)}]
|
||||
[quo2/text {:style {:margin-top 16} :size :paragraph-1 :weight :medium}
|
||||
|
@ -14,7 +14,7 @@
|
||||
[chat-id]
|
||||
(fn []
|
||||
(rf/dispatch [:dismiss-keyboard])
|
||||
(rf/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
|
||||
(rf/dispatch [:chat/navigate-to-chat chat-id])
|
||||
(rf/dispatch [:search/home-filter-changed nil])))
|
||||
|
||||
(defn truncate-literal
|
||||
|
@ -37,7 +37,7 @@
|
||||
[rn/flat-list
|
||||
{:key-fn #(or (:chat-id %) (:public-key %) (:id %))
|
||||
:get-item-layout get-item-layout
|
||||
:on-end-reached #(re-frame/dispatch [:chat.ui/show-more-chats])
|
||||
:on-end-reached #(re-frame/dispatch [:chat/show-more-chats])
|
||||
:keyboard-should-persist-taps :always
|
||||
:data items
|
||||
:render-fn chat-list-item/chat-list-item}])))
|
||||
|
@ -173,7 +173,7 @@
|
||||
[quo/floating-shell-button
|
||||
(merge {:jump-to
|
||||
{:on-press #(do
|
||||
(rf/dispatch [:close-chat true])
|
||||
(rf/dispatch [:chat/close true])
|
||||
(rf/dispatch [:shell/navigate-to-jump-to]))
|
||||
:label (i18n/label :t/jump-to)}}
|
||||
(when @show-floating-scroll-down-button
|
||||
|
@ -20,7 +20,7 @@
|
||||
[]
|
||||
(when (and (not @navigation.state/curr-modal) (= (get @re-frame.db/app-db :view-id) :chat))
|
||||
(rn/hw-back-remove-listener navigate-back-handler)
|
||||
(rf/dispatch [:close-chat])
|
||||
(rf/dispatch [:chat/close])
|
||||
(rf/dispatch [:navigate-back])
|
||||
;; If true is not returned back button event will bubble up,
|
||||
;; and will call system back button action
|
||||
@ -51,7 +51,7 @@
|
||||
1000)})
|
||||
|
||||
:left-section {:on-press #(do
|
||||
(rf/dispatch [:close-chat])
|
||||
(rf/dispatch [:chat/close])
|
||||
(rf/dispatch [:navigate-back]))
|
||||
:icon :i/arrow-left
|
||||
:accessibility-label :back-button}
|
||||
|
@ -13,7 +13,8 @@
|
||||
[status-im2.db :as db]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.datetime :as datetime]
|
||||
status-im2.contexts.syncing.events))
|
||||
status-im2.contexts.syncing.events
|
||||
status-im2.contexts.chat.events))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:now
|
||||
|
@ -3,7 +3,7 @@
|
||||
[quo.design-system.colors :as colors]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.add-new.db :as db]
|
||||
[status-im.chat.models :as chat.models]
|
||||
[status-im2.contexts.chat.events :as chat.events]
|
||||
[status-im.chat.models.mentions :as mentions]
|
||||
[status-im.communities.core :as communities]
|
||||
[status-im.group-chats.core :as group-chat]
|
||||
@ -138,13 +138,6 @@
|
||||
(fn [[chat-id inputs]]
|
||||
(get inputs chat-id)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/timeline-chat-input
|
||||
:<- [:chat/inputs]
|
||||
:<- [:multiaccount/public-key]
|
||||
(fn [[inputs public-key]]
|
||||
(get inputs (chat.models/profile-chat-topic public-key))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/sending-image
|
||||
:<- [:chats/current-chat-id]
|
||||
@ -177,15 +170,15 @@
|
||||
inputs]]
|
||||
(when current-chat
|
||||
(cond-> current-chat
|
||||
(chat.models/public-chat? current-chat)
|
||||
(chat.events/public-chat? current-chat)
|
||||
(assoc :show-input? true)
|
||||
|
||||
(and (chat.models/group-chat? current-chat)
|
||||
(and (chat.events/group-chat? current-chat)
|
||||
(group-chats.db/member? my-public-key current-chat))
|
||||
(assoc :show-input? true
|
||||
:member? true)
|
||||
|
||||
(and (chat.models/community-chat? current-chat)
|
||||
(and (chat.events/community-chat? current-chat)
|
||||
(communities/can-post? community my-public-key (:chat-id current-chat)))
|
||||
(assoc :show-input? true)
|
||||
|
||||
@ -239,14 +232,8 @@
|
||||
:current-chat/one-to-one-chat?
|
||||
:<- [:chats/current-raw-chat]
|
||||
(fn [current-chat]
|
||||
(not (or (chat.models/group-chat? current-chat)
|
||||
(chat.models/public-chat? current-chat)))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/current-profile-chat
|
||||
:<- [:contacts/current-contact-identity]
|
||||
(fn [identity]
|
||||
(chat.models/profile-chat-topic identity)))
|
||||
(not (or (chat.events/group-chat? current-chat)
|
||||
(chat.events/public-chat? current-chat)))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/photo-path
|
||||
@ -266,7 +253,7 @@
|
||||
:<- [:chats/home-list-chats]
|
||||
(fn [chats _]
|
||||
(reduce (fn [{:keys [public other]} {:keys [unviewed-messages-count public?] :as chat}]
|
||||
(if (or public? (chat.models/community-chat? chat))
|
||||
(if (or public? (chat.events/community-chat? chat))
|
||||
{:public (+ public unviewed-messages-count)
|
||||
:other other}
|
||||
{:other (+ other unviewed-messages-count)
|
||||
|
@ -1,11 +1,132 @@
|
||||
(ns status-im2.subs.chat.messages
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.chat.db :as chat.db]
|
||||
[status-im2.contexts.chat.messages.list.events :as models.message-list]
|
||||
[status-im.chat.models.reactions :as models.reactions]
|
||||
[utils.datetime :as datetime]
|
||||
[status-im2.constants :as constants]))
|
||||
|
||||
(defn intersperse-datemark
|
||||
"Reduce step which expects the input list of messages to be sorted by clock value.
|
||||
It makes best effort to group them by day.
|
||||
We cannot sort them by :timestamp, as that represents the clock of the sender
|
||||
and we have no guarantees on the order.
|
||||
We naively and arbitrarly group them assuming that out-of-order timestamps
|
||||
fall in the previous bucket.
|
||||
A sends M1 to B with timestamp 2000-01-01T00:00:00
|
||||
B replies M2 with timestamp 1999-12-31-23:59:59
|
||||
M1 needs to be displayed before M2
|
||||
so we bucket both in 1999-12-31"
|
||||
[{:keys [acc last-timestamp last-datemark]} {:keys [whisper-timestamp datemark] :as msg}]
|
||||
(cond
|
||||
(empty? acc) ; initial element
|
||||
{:last-timestamp whisper-timestamp
|
||||
:last-datemark datemark
|
||||
:acc (conj acc msg)}
|
||||
|
||||
(and (not= last-datemark datemark) ; not the same day
|
||||
(< whisper-timestamp last-timestamp)) ; not out-of-order
|
||||
{:last-timestamp whisper-timestamp
|
||||
:last-datemark datemark
|
||||
:acc (conj acc
|
||||
{:value last-datemark ; intersperse datemark message
|
||||
:type :datemark}
|
||||
msg)}
|
||||
:else
|
||||
{:last-timestamp (min whisper-timestamp last-timestamp) ; use last datemark
|
||||
:last-datemark last-datemark
|
||||
:acc (conj acc (assoc msg :datemark last-datemark))}))
|
||||
|
||||
(defn add-datemarks
|
||||
"Add a datemark in between an ordered seq of messages when two datemarks are not
|
||||
the same. Ignore messages with out-of-order timestamps"
|
||||
[messages]
|
||||
(when (seq messages)
|
||||
(let [messages-with-datemarks (:acc (reduce intersperse-datemark {:acc []} messages))]
|
||||
; Append last datemark
|
||||
(conj messages-with-datemarks
|
||||
{:value (:datemark (peek messages-with-datemarks))
|
||||
:type :datemark}))))
|
||||
|
||||
(defn last-gap
|
||||
"last-gap is a special gap that is put last in the message stream"
|
||||
[chat-id synced-from]
|
||||
{:message-id "0x123"
|
||||
:message-type constants/message-type-gap
|
||||
:chat-id chat-id
|
||||
:content-type constants/content-type-gap
|
||||
:gap-ids #{:first-gap}
|
||||
:gap-parameters {:from synced-from}})
|
||||
|
||||
(defn collapse-gaps
|
||||
"collapse-gaps will take an array of messages and collapse any gap next to
|
||||
each other in a single gap.
|
||||
It will also append one last gap if the last message is a non-gap"
|
||||
[messages chat-id synced-from now chat-type joined loading-messages?]
|
||||
(let [messages-with-gaps (reduce
|
||||
(fn [acc {:keys [gap-parameters message-id] :as message}]
|
||||
(let [last-element (peek acc)]
|
||||
(cond
|
||||
;; If it's a message, just add
|
||||
(empty? gap-parameters)
|
||||
(conj acc message)
|
||||
|
||||
;; Both are gaps, merge them
|
||||
(and
|
||||
(seq (:gap-parameters last-element))
|
||||
(seq gap-parameters))
|
||||
(conj (pop acc) (update last-element :gap-ids conj message-id))
|
||||
|
||||
;; it's a gap
|
||||
:else
|
||||
(conj acc (assoc message :gap-ids #{message-id})))))
|
||||
[]
|
||||
messages)]
|
||||
(if (or loading-messages? ; it's loading messages from the database
|
||||
(nil? synced-from) ; it's still syncing
|
||||
(= constants/timeline-chat-type chat-type) ; it's a timeline chat
|
||||
(= constants/profile-chat-type chat-type) ; it's a profile chat
|
||||
(and (not (nil? synced-from)) ; it's not more than a month
|
||||
(<= synced-from (- (quot now 1000) constants/one-month)))
|
||||
(and (= constants/private-group-chat-type chat-type) ; it's a private group chat
|
||||
(or (not (pos? joined)) ; we haven't joined
|
||||
(>= (quot joined 1000) synced-from))) ; the history goes before we joined
|
||||
(:gap-ids (peek messages-with-gaps))) ; there's already a gap on top of the chat history
|
||||
messages-with-gaps ; don't add an extra gap
|
||||
(conj messages-with-gaps (last-gap chat-id synced-from)))))
|
||||
|
||||
(defn hydrate-messages
|
||||
"Pull data from messages and add it to the sorted list"
|
||||
([message-list messages] (hydrate-messages message-list messages {}))
|
||||
([message-list messages pinned-messages]
|
||||
(keep #(if (= :message (% :type))
|
||||
(when-let [message (messages (% :message-id))]
|
||||
(let [pinned-message (get pinned-messages (% :message-id))
|
||||
pinned (if pinned-message true (some? (message :pinned-by)))
|
||||
pinned-by (when pinned (or (message :pinned-by) (pinned-message :pinned-by)))
|
||||
message (assoc message :pinned pinned :pinned-by pinned-by)]
|
||||
(merge message %)))
|
||||
%)
|
||||
message-list)))
|
||||
|
||||
(defn albumize-messages
|
||||
[messages]
|
||||
(get (reduce (fn [{:keys [messages albums]} message]
|
||||
(let [album-id (when (:albumize? message) (:album-id message))
|
||||
albums (cond-> albums album-id (update album-id conj message))
|
||||
messages (if (and album-id (> (count (get albums album-id)) 3))
|
||||
(conj (filterv #(not= album-id (:album-id %)) messages)
|
||||
{:album (get albums album-id)
|
||||
:album-id album-id
|
||||
:message-id album-id
|
||||
:content-type constants/content-type-album})
|
||||
(conj messages message))]
|
||||
{:messages messages
|
||||
:albums albums}))
|
||||
{:messages []
|
||||
:albums {}}
|
||||
messages)
|
||||
:messages))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/chat-messages
|
||||
:<- [:messages/messages]
|
||||
@ -81,20 +202,6 @@
|
||||
(fn [pin-message-lists [_ chat-id]]
|
||||
(get pin-message-lists chat-id)))
|
||||
|
||||
(defn hydrate-messages
|
||||
"Pull data from messages and add it to the sorted list"
|
||||
([message-list messages] (hydrate-messages message-list messages {}))
|
||||
([message-list messages pinned-messages]
|
||||
(keep #(if (= :message (% :type))
|
||||
(when-let [message (messages (% :message-id))]
|
||||
(let [pinned-message (get pinned-messages (% :message-id))
|
||||
pinned (if pinned-message true (some? (message :pinned-by)))
|
||||
pinned-by (when pinned (or (message :pinned-by) (pinned-message :pinned-by)))
|
||||
message (assoc message :pinned pinned :pinned-by pinned-by)]
|
||||
(merge message %)))
|
||||
%)
|
||||
message-list)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/chat-no-messages?
|
||||
(fn [[_ chat-id] _]
|
||||
@ -102,25 +209,6 @@
|
||||
(fn [messages]
|
||||
(empty? messages)))
|
||||
|
||||
(defn albumize-messages
|
||||
[messages]
|
||||
(get (reduce (fn [{:keys [messages albums]} message]
|
||||
(let [album-id (when (:albumize? message) (:album-id message))
|
||||
albums (cond-> albums album-id (update album-id conj message))
|
||||
messages (if (and album-id (> (count (get albums album-id)) 3))
|
||||
(conj (filterv #(not= album-id (:album-id %)) messages)
|
||||
{:album (get albums album-id)
|
||||
:album-id album-id
|
||||
:message-id album-id
|
||||
:content-type constants/content-type-album})
|
||||
(conj messages message))]
|
||||
{:messages messages
|
||||
:albums albums}))
|
||||
{:messages []
|
||||
:albums {}}
|
||||
messages)
|
||||
:messages))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/raw-chat-messages-stream
|
||||
(fn [[_ chat-id] _]
|
||||
@ -138,41 +226,12 @@
|
||||
(if (and (empty? message-list-seq) loading-messages?)
|
||||
[]
|
||||
(-> message-list-seq
|
||||
(chat.db/add-datemarks)
|
||||
(add-datemarks)
|
||||
(hydrate-messages messages pin-messages)
|
||||
(chat.db/collapse-gaps chat-id
|
||||
synced-from
|
||||
(datetime/timestamp)
|
||||
chat-type
|
||||
joined
|
||||
loading-messages?)
|
||||
(collapse-gaps chat-id
|
||||
synced-from
|
||||
(datetime/timestamp)
|
||||
chat-type
|
||||
joined
|
||||
loading-messages?)
|
||||
(albumize-messages))))))
|
||||
|
||||
;;we want to keep data unchanged so react doesn't change component when we leave screen
|
||||
(def memo-profile-messages-stream (atom nil))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/profile-messages-stream
|
||||
(fn [[_ chat-id] _]
|
||||
[(re-frame/subscribe [:chats/raw-chat-messages-stream chat-id])
|
||||
(re-frame/subscribe [:chats/chat-no-messages? chat-id])
|
||||
(re-frame/subscribe [:view-id])])
|
||||
(fn [[messages empty view-id]]
|
||||
(when (or (= view-id :profile) empty)
|
||||
(reset! memo-profile-messages-stream messages))
|
||||
@memo-profile-messages-stream))
|
||||
|
||||
(def memo-timeline-messages-stream (atom nil))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/timeline-messages-stream
|
||||
:<- [:chats/message-list constants/timeline-chat-id]
|
||||
:<- [:chats/chat-messages constants/timeline-chat-id]
|
||||
:<- [:view-id]
|
||||
(fn [[message-list messages view-id]]
|
||||
(if (= view-id :status)
|
||||
(let [res (-> (models.message-list/->seq message-list)
|
||||
(hydrate-messages messages))]
|
||||
(reset! memo-timeline-messages-stream res)
|
||||
res)
|
||||
@memo-timeline-messages-stream)))
|
||||
|
@ -23,3 +23,31 @@
|
||||
(deftest albumize-messages
|
||||
(testing "Finding albums in the messages list"
|
||||
(is (= (messages/albumize-messages messages-state) messages-albumized-state))))
|
||||
|
||||
(deftest intersperse-datemarks
|
||||
(testing "it mantains the order even when timestamps are across days"
|
||||
(let [message-1 {:datemark "Dec 31, 1999"
|
||||
:whisper-timestamp 946641600000} ; 1999}
|
||||
message-2 {:datemark "Jan 1, 2000"
|
||||
:whisper-timestamp 946728000000} ; 2000 this will displayed in 1999
|
||||
message-3 {:datemark "Dec 31, 1999"
|
||||
:whisper-timestamp 946641600000} ; 1999
|
||||
message-4 {:datemark "Jan 1, 2000"
|
||||
:whisper-timestamp 946728000000} ; 2000
|
||||
ordered-messages [message-4
|
||||
message-3
|
||||
message-2
|
||||
message-1]
|
||||
[m1 d1 m2 m3 m4 d2] (messages/add-datemarks ordered-messages)]
|
||||
(is (= "Jan 1, 2000"
|
||||
(:datemark m1)))
|
||||
(is (= {:type :datemark
|
||||
:value "Jan 1, 2000"}
|
||||
d1))
|
||||
(is (= "Dec 31, 1999"
|
||||
(:datemark m2)
|
||||
(:datemark m3)
|
||||
(:datemark m4)))
|
||||
(is (= {:type :datemark
|
||||
:value "Dec 31, 1999"}
|
||||
d2)))))
|
||||
|
@ -70,7 +70,7 @@
|
||||
(str profile-picture "&addRing=0"))}
|
||||
:customization-color (or (:customization-color contact) :primary)
|
||||
:on-close #(re-frame/dispatch [:shell/close-switcher-card id])
|
||||
:on-press #(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 id true])
|
||||
:on-press #(re-frame/dispatch [:chat/navigate-to-chat id true])
|
||||
:content (get-card-content chat communities)}))
|
||||
|
||||
(defn private-group-chat-card
|
||||
@ -79,7 +79,7 @@
|
||||
:avatar-params {}
|
||||
:customization-color (or (:customization-color chat) :primary)
|
||||
:on-close #(re-frame/dispatch [:shell/close-switcher-card id])
|
||||
:on-press #(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 id true])
|
||||
:on-press #(re-frame/dispatch [:chat/navigate-to-chat id true])
|
||||
:content (get-card-content chat communities)})
|
||||
|
||||
(defn community-card
|
||||
@ -104,7 +104,7 @@
|
||||
:on-press (fn []
|
||||
(re-frame/dispatch [:navigate-to-nav2 :community {:community-id community-id}])
|
||||
(js/setTimeout
|
||||
#(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 channel-id true])
|
||||
#(re-frame/dispatch [:chat/navigate-to-chat channel-id true])
|
||||
100))}))
|
||||
|
||||
(re-frame/reg-sub
|
||||
|
Loading…
x
Reference in New Issue
Block a user