Add system messages to group chats
Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
parent
a8481ab3e1
commit
7aa597517e
2
.env
2
.env
|
@ -8,7 +8,7 @@ POW_TARGET=0.002
|
|||
POW_TIME=1
|
||||
DEFAULT_NETWORK=mainnet_rpc
|
||||
DEBUG_WEBVIEW=1
|
||||
GROUP_CHATS_ENABLED=0
|
||||
GROUP_CHATS_ENABLED=1
|
||||
PAIRING_ENABLED=0
|
||||
CACHED_WEBVIEWS_ENABLED=1
|
||||
EXTENSIONS=1
|
||||
|
|
2
.env.e2e
2
.env.e2e
|
@ -7,6 +7,6 @@ POW_TARGET=0.002
|
|||
POW_TIME=1
|
||||
DEFAULT_NETWORK=testnet_rpc
|
||||
DEBUG_WEBVIEW=1
|
||||
GROUP_CHATS_ENABLED=0
|
||||
GROUP_CHATS_ENABLED=1
|
||||
EXTENSIONS=1
|
||||
PFS_ENCRYPTION_ENABLED=0
|
||||
|
|
|
@ -8,7 +8,7 @@ POW_TARGET=0.002
|
|||
POW_TIME=1
|
||||
DEFAULT_NETWORK=mainnet_rpc
|
||||
DEBUG_WEBVIEW=1
|
||||
GROUP_CHATS_ENABLED=0
|
||||
GROUP_CHATS_ENABLED=1
|
||||
MAINNET_WARNING_ENABLED=1
|
||||
CACHED_WEBVIEWS_ENABLED=1
|
||||
EXTENSIONS=1
|
||||
|
|
|
@ -8,7 +8,7 @@ POW_TARGET=0.002
|
|||
POW_TIME=1
|
||||
DEFAULT_NETWORK=mainnet_rpc
|
||||
DEBUG_WEBVIEW=1
|
||||
GROUP_CHATS_ENABLED=0
|
||||
GROUP_CHATS_ENABLED=1
|
||||
PAIRING_ENABLED=0
|
||||
MAINNET_WARNING_ENABLED=1
|
||||
EXTENSIONS=1
|
||||
|
|
|
@ -57,8 +57,7 @@
|
|||
(into (or message-references '()))
|
||||
(sort-references (get-in db [:chats chat-id :messages]))))))
|
||||
db
|
||||
(group-by (comp time/day-relative :timestamp)
|
||||
(filter :show? messages)))})
|
||||
(group-by (comp time/day-relative :timestamp) messages))})
|
||||
|
||||
(fx/defn group-messages
|
||||
[{:keys [db]}]
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
[status-im.utils.config :as config]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.datetime :as time]
|
||||
[status-im.group-chats.core :as group-chats]
|
||||
[status-im.transport.message.group-chat :as message.group-chat]
|
||||
[status-im.chat.models :as chat-model]
|
||||
[status-im.chat.models.loading :as chat-loading]
|
||||
[status-im.chat.models.message-content :as message-content]
|
||||
|
@ -26,6 +26,15 @@
|
|||
[status-im.utils.fx :as fx]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn- wrap-group-message
|
||||
"Wrap a group message in a membership update"
|
||||
[cofx chat-id message]
|
||||
(when-let [chat (get-in cofx [:db :chats chat-id])]
|
||||
(message.group-chat/map->GroupMembershipUpdate.
|
||||
{:chat-id chat-id
|
||||
:membership-updates (:membership-updates chat)
|
||||
:message message})))
|
||||
|
||||
(defn- prepare-message
|
||||
[{:keys [content] :as message} chat-id current-chat?]
|
||||
;; TODO janherich: enable the animations again once we can do them more efficiently
|
||||
|
@ -66,8 +75,11 @@
|
|||
status)
|
||||
:data-store/tx [(user-statuses-store/save-status-tx status)]}))
|
||||
|
||||
(defn add-outgoing-status [{:keys [from] :as message} {:keys [db]}]
|
||||
(assoc message :outgoing (= from (:current-public-key db))))
|
||||
(defn add-outgoing-status [{:keys [from message-type] :as message} {:keys [db]}]
|
||||
(assoc message
|
||||
:outgoing
|
||||
(and (= from (:current-public-key db))
|
||||
(not= :system-message message-type))))
|
||||
|
||||
(fx/defn add-message
|
||||
[{:keys [db] :as cofx} batch? {:keys [chat-id message-id clock-value timestamp content from] :as message} current-chat?]
|
||||
|
@ -197,15 +209,17 @@
|
|||
groups-fx-fns (map #(update-group-messages chat->message %) chat-ids)]
|
||||
(apply fx/merge cofx (concat chats-fx-fns messages-fx-fns groups-fx-fns))))
|
||||
|
||||
(defn system-message [chat-id message-id timestamp content]
|
||||
{:message-id message-id
|
||||
:chat-id chat-id
|
||||
:from constants/system
|
||||
:username constants/system
|
||||
:timestamp timestamp
|
||||
:show? true
|
||||
:content content
|
||||
:content-type constants/content-type-text})
|
||||
(defn system-message [{:keys [now] :as cofx} {:keys [clock-value chat-id content from]}]
|
||||
(let [{:keys [last-clock-value]} (get-in cofx [:db :chats chat-id])
|
||||
message {:chat-id chat-id
|
||||
:from from
|
||||
:timestamp now
|
||||
:clock-value (or clock-value
|
||||
(utils.clocks/send last-clock-value))
|
||||
:content content
|
||||
:message-type :system-message
|
||||
:content-type constants/content-type-status}]
|
||||
(assoc message :message-id (transport.utils/message-id message))))
|
||||
|
||||
(defn group-message? [{:keys [message-type]}]
|
||||
(#{:group-user-message :public-group-user-message} message-type))
|
||||
|
@ -218,7 +232,7 @@
|
|||
{:dispatch-later [{:ms 10000
|
||||
:dispatch [:message/update-message-status chat-id message-id :not-sent]}]}
|
||||
(let [wrapped-record (if (= (:message-type send-record) :group-user-message)
|
||||
(group-chats/wrap-group-message cofx chat-id send-record)
|
||||
(wrap-group-message cofx chat-id send-record)
|
||||
send-record)]
|
||||
|
||||
(protocol/send wrapped-record chat-id cofx))))
|
||||
|
@ -296,6 +310,10 @@
|
|||
:data-store/tx [(messages-store/delete-message-tx message-id)]}
|
||||
(remove-message-from-group chat-id (get-in db [:chats chat-id :messages message-id]))))
|
||||
|
||||
(fx/defn add-system-messages [cofx messages]
|
||||
(let [messages-fx (map #(add-message false (system-message cofx %) true) messages)]
|
||||
(apply fx/merge cofx messages-fx)))
|
||||
|
||||
(fx/defn send-message
|
||||
[{:keys [db now] :as cofx} {:keys [chat-id] :as message}]
|
||||
(let [{:keys [current-public-key chats]} db
|
||||
|
@ -304,8 +322,7 @@
|
|||
(assoc :from current-public-key
|
||||
:timestamp now
|
||||
:clock-value (utils.clocks/send
|
||||
last-clock-value)
|
||||
:show? true)
|
||||
last-clock-value))
|
||||
(add-message-type chat))]
|
||||
(upsert-and-send cofx message-data)))
|
||||
|
||||
|
|
|
@ -181,14 +181,16 @@
|
|||
message-groups))
|
||||
|
||||
(defn- set-previous-message-info [stream]
|
||||
(let [{:keys [display-photo?] :as previous-message} (peek stream)]
|
||||
(let [{:keys [display-photo? message-type] :as previous-message} (peek stream)]
|
||||
(conj (pop stream) (assoc previous-message
|
||||
:display-username? display-photo?
|
||||
:display-username? (and display-photo?
|
||||
(not= :system-message message-type))
|
||||
:first-in-group? true))))
|
||||
|
||||
(defn display-photo? [{:keys [outgoing message-type]}]
|
||||
(and (not outgoing)
|
||||
(not= message-type :user-message)))
|
||||
(or (= :system-message message-type)
|
||||
(and (not outgoing)
|
||||
(not (= :user-message message-type)))))
|
||||
|
||||
; any message that comes after this amount of ms will be grouped separately
|
||||
(def ^:private group-ms 60000)
|
||||
|
@ -201,7 +203,8 @@
|
|||
(let [previous-message (peek stream)
|
||||
; Was the previous message from a different author or this message
|
||||
; comes after x ms
|
||||
last-in-group? (or (not= from (:from previous-message))
|
||||
last-in-group? (or (= :system-message message-type)
|
||||
(not= from (:from previous-message))
|
||||
(> (- (:timestamp previous-message) timestamp) group-ms))
|
||||
same-direction? (= outgoing (:outgoing previous-message))
|
||||
; Have we seen an outgoing message already?
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
(def content-type-emoji "emoji")
|
||||
|
||||
(def desktop-content-types
|
||||
#{content-type-text content-type-emoji})
|
||||
#{content-type-text content-type-emoji content-type-status})
|
||||
|
||||
(def min-password-length 6)
|
||||
(def max-chat-name-length 20)
|
||||
|
|
|
@ -30,10 +30,14 @@
|
|||
;; it's not in the contact list at all
|
||||
(nil? pending?))))
|
||||
|
||||
(defn- build-contact [whisper-id {{:keys [chats] :contacts/keys [contacts]} :db}]
|
||||
(assoc (or (get contacts whisper-id)
|
||||
(utils.contacts/whisper-id->new-contact whisper-id))
|
||||
:address (utils.contacts/public-key->address whisper-id)))
|
||||
(defn build-contact [{{:keys [chats current-public-key]
|
||||
:account/keys [account]
|
||||
:contacts/keys [contacts]} :db} whisper-id]
|
||||
(cond-> (assoc (or (get contacts whisper-id)
|
||||
(utils.contacts/whisper-id->new-contact whisper-id))
|
||||
:address (utils.contacts/public-key->address whisper-id))
|
||||
|
||||
(= whisper-id current-public-key) (assoc :name (:name account))))
|
||||
|
||||
(defn- own-info [db]
|
||||
(let [{:keys [name photo-path address]} (:account/account db)
|
||||
|
@ -62,10 +66,11 @@
|
|||
(protocol/send (message.contact/map->ContactRequest (own-info db)) whisper-identity cofx))))
|
||||
|
||||
(fx/defn add-contact [{:keys [db] :as cofx} whisper-id]
|
||||
(let [contact (build-contact whisper-id cofx)]
|
||||
(fx/merge cofx
|
||||
(add-new-contact contact)
|
||||
(send-contact-request contact))))
|
||||
(when (not= (get-in db [:account/account :public-key]) whisper-id)
|
||||
(let [contact (build-contact cofx whisper-id)]
|
||||
(fx/merge cofx
|
||||
(add-new-contact contact)
|
||||
(send-contact-request contact)))))
|
||||
|
||||
(fx/defn add-tag
|
||||
"add a tag to the contact"
|
||||
|
|
|
@ -105,7 +105,9 @@
|
|||
(reg-sub :get-all-contacts-not-in-current-chat
|
||||
:<- [:query-current-chat-contacts remove]
|
||||
(fn [contacts]
|
||||
(remove :dapp? contacts)))
|
||||
(->> contacts
|
||||
(remove :dapp?)
|
||||
(sort-by (comp clojure.string/lower-case :name)))))
|
||||
|
||||
(defn get-all-contacts-in-group-chat [members contacts current-account]
|
||||
(let [current-account-contact (-> current-account
|
||||
|
|
|
@ -136,11 +136,3 @@
|
|||
(fn [realm]
|
||||
(core/delete realm (core/get-by-field realm :message :chat-id chat-id))
|
||||
(core/delete realm (core/get-by-field realm :user-status :chat-id chat-id))))
|
||||
|
||||
(defn hide-messages-tx
|
||||
"Returns tx function for hiding messages for given chat-id"
|
||||
[chat-id]
|
||||
(fn [realm]
|
||||
(.map (core/get-by-field realm :message :chat-id chat-id)
|
||||
(fn [msg _ _]
|
||||
(aset msg "show?" false)))))
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.utils.clocks :as utils.clocks]
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.contact.core :as models.contact]
|
||||
[status-im.native-module.core :as native-module]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.transport.db :as transport.db]
|
||||
|
@ -39,11 +41,7 @@
|
|||
(defn get-last-clock-value
|
||||
"Given a chat id get the last clock value of an event"
|
||||
[cofx chat-id]
|
||||
(->> (get-in cofx [:db :chats chat-id :membership-updates])
|
||||
(mapcat :events)
|
||||
(map :clock-value)
|
||||
sort
|
||||
last))
|
||||
(->> (get-in cofx [:db :chats chat-id :last-clock-value])))
|
||||
|
||||
(defn- parse-response [response-js]
|
||||
(-> response-js
|
||||
|
@ -100,15 +98,6 @@
|
|||
(not= #{from} admins))
|
||||
false)))
|
||||
|
||||
(defn wrap-group-message
|
||||
"Wrap a group message in a membership update"
|
||||
[cofx chat-id message]
|
||||
(when-let [chat (get-in cofx [:db :chats chat-id])]
|
||||
(message.group-chat/map->GroupMembershipUpdate.
|
||||
{:chat-id chat-id
|
||||
:membership-updates (:membership-updates chat)
|
||||
:message message})))
|
||||
|
||||
(defn send-membership-update
|
||||
"Send a membership update to all participants but the sender"
|
||||
([cofx payload chat-id]
|
||||
|
@ -236,6 +225,15 @@
|
|||
(assoc-in [:group-chat-profile/profile :valid-name?] (valid-name? name))
|
||||
(assoc-in [:group-chat-profile/profile :name] name))})
|
||||
|
||||
(defn extract-creator
|
||||
"Takes a chat as an input, returns the creator"
|
||||
[{:keys [membership-updates]}]
|
||||
(->> membership-updates
|
||||
(filter (fn [{:keys [events]}]
|
||||
(some #(= "chat-created" (:type %)) events)))
|
||||
first
|
||||
:from))
|
||||
|
||||
(fx/defn handle-name-changed
|
||||
"Store name in profile scratchpad"
|
||||
[cofx new-chat-name]
|
||||
|
@ -244,27 +242,47 @@
|
|||
(fx/defn save
|
||||
"Save chat from edited profile"
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [current-chat-id (get-in cofx [:db :current-chat-id])
|
||||
new-name (get-in cofx [:db :group-chat-profile/profile :name])]
|
||||
(let [current-chat-id (get-in cofx [:db :current-chat-id])
|
||||
my-public-key (get-in db [:account/account :public-key])
|
||||
last-clock-value (get-last-clock-value cofx current-chat-id)
|
||||
new-name (get-in cofx [:db :group-chat-profile/profile :name])
|
||||
name-changed-event {:type "name-changed"
|
||||
:name new-name
|
||||
:clock-value (utils.clocks/send last-clock-value)}]
|
||||
(when (valid-name? new-name)
|
||||
(fx/merge cofx
|
||||
{:db (assoc db :group-chat-profile/editing? false)}
|
||||
(models.chat/upsert-chat {:chat-id current-chat-id
|
||||
:name new-name})))))
|
||||
{:db (assoc db
|
||||
:group-chat-profile/editing?
|
||||
false)
|
||||
:group-chats/sign-membership {:chat-id current-chat-id
|
||||
:from my-public-key
|
||||
:events [name-changed-event]}}))))
|
||||
|
||||
(defn process-event
|
||||
"Add/remove an event to a group"
|
||||
[group {:keys [type member members chat-id from name] :as event}]
|
||||
"Add/remove an event to a group, carrying the clock-value at which it happened"
|
||||
[group {:keys [type member members chat-id clock-value from name] :as event}]
|
||||
(if (valid-event? group event)
|
||||
(case type
|
||||
"chat-created" {:name name
|
||||
:admins #{from}
|
||||
:contacts #{from}}
|
||||
"name-changed" (assoc group :name name)
|
||||
"members-added" (update group :contacts clojure.set/union (into #{} members))
|
||||
"admins-added" (update group :admins clojure.set/union (into #{} members))
|
||||
"member-removed" (update group :contacts disj member)
|
||||
"admin-removed" (update group :admins disj member))
|
||||
"chat-created" {:name name
|
||||
:created-at clock-value
|
||||
:admins #{from}
|
||||
:contacts #{from}}
|
||||
"name-changed" (assoc group
|
||||
:name name
|
||||
:name-changed-by from
|
||||
:name-changed-at clock-value)
|
||||
"members-added" (as-> group $
|
||||
(update $ :contacts clojure.set/union (set members))
|
||||
(reduce (fn [acc member] (assoc-in acc [member :added] clock-value)) $ members))
|
||||
"admins-added" (as-> group $
|
||||
(update $ :admins clojure.set/union (set members))
|
||||
(reduce (fn [acc member] (assoc-in acc [member :admin-added] clock-value)) $ members))
|
||||
"member-removed" (-> group
|
||||
(update :contacts disj member)
|
||||
(assoc-in [member :removed] clock-value))
|
||||
"admin-removed" (-> group
|
||||
(update :admins disj member)
|
||||
(assoc-in [member :admin-removed] clock-value)))
|
||||
group))
|
||||
|
||||
(defn build-group
|
||||
|
@ -277,24 +295,78 @@
|
|||
{:admins #{}
|
||||
:contacts #{}})))
|
||||
|
||||
(fx/defn update-membership
|
||||
"Upsert chat when version is greater or not existing"
|
||||
[cofx previous-chat {:keys [chat-id] :as new-chat}]
|
||||
(let [all-updates (clojure.set/union (into #{} (:membership-updates previous-chat))
|
||||
(into #{} (:membership-updates new-chat)))
|
||||
unwrapped-events (mapcat
|
||||
(fn [{:keys [events from]}]
|
||||
(map #(assoc % :from from) events))
|
||||
all-updates)
|
||||
new-group (build-group unwrapped-events)]
|
||||
(models.chat/upsert-chat cofx
|
||||
{:chat-id chat-id
|
||||
:name (:name new-group)
|
||||
:is-active (get previous-chat :is-active true)
|
||||
:group-chat true
|
||||
:membership-updates (into [] all-updates)
|
||||
:admins (:admins new-group)
|
||||
:contacts (:contacts new-group)})))
|
||||
(defn membership-changes->system-messages [cofx
|
||||
clock-values
|
||||
{:keys [chat-id
|
||||
chat-name
|
||||
creator
|
||||
members-added
|
||||
name-changed?
|
||||
members-removed]}]
|
||||
(let [get-contact (partial models.contact/build-contact cofx)
|
||||
format-message (fn [contact text clock-value]
|
||||
{:chat-id chat-id
|
||||
:content {:text text}
|
||||
:clock-value clock-value
|
||||
:from (:whisper-identity contact)})
|
||||
creator-contact (when creator (get-contact creator))
|
||||
name-changed-author (when name-changed? (get-contact (:name-changed-by clock-values)))
|
||||
contacts-added (map
|
||||
get-contact
|
||||
(disj members-added creator))
|
||||
contacts-removed (map
|
||||
get-contact
|
||||
members-removed)]
|
||||
(cond-> []
|
||||
creator-contact (conj (format-message creator-contact
|
||||
(i18n/label :t/group-chat-created
|
||||
{:name chat-name
|
||||
:member (:name creator-contact)})
|
||||
(:created-at clock-values)))
|
||||
name-changed? (conj (format-message name-changed-author
|
||||
(i18n/label :t/group-chat-name-changed
|
||||
{:name chat-name
|
||||
:member (:name name-changed-author)})
|
||||
(:name-changed-at clock-values)))
|
||||
(seq members-added) (concat (map #(format-message
|
||||
%
|
||||
(i18n/label :t/group-chat-member-added {:member (:name %)})
|
||||
(get-in clock-values [(:whisper-identity %) :added]))
|
||||
contacts-added))
|
||||
(seq members-removed) (concat (map #(format-message
|
||||
%
|
||||
(i18n/label :t/group-chat-member-removed {:member (:name %)})
|
||||
(get-in clock-values [(:whisper-identity %) :removed]))
|
||||
contacts-removed)))))
|
||||
|
||||
(fx/defn add-system-messages [cofx chat-id previous-chat clock-values]
|
||||
(let [current-chat (get-in cofx [:db :chats chat-id])
|
||||
current-public-key (get-in cofx [:db :current-public-key])
|
||||
name-changed? (and (seq previous-chat)
|
||||
(not= (:name previous-chat) (:name current-chat)))
|
||||
members-added (clojure.set/difference (:contacts current-chat) (:contacts previous-chat))
|
||||
members-removed (clojure.set/difference (:contacts previous-chat) (:contacts current-chat))
|
||||
membership-changes (cond-> {:chat-id chat-id
|
||||
:name-changed? name-changed?
|
||||
:chat-name (:name current-chat)
|
||||
:members-added members-added
|
||||
:members-removed members-removed}
|
||||
(nil? previous-chat)
|
||||
(assoc :creator (extract-creator current-chat)))]
|
||||
(when (or name-changed?
|
||||
(seq members-added)
|
||||
(seq members-removed))
|
||||
(->> membership-changes
|
||||
(membership-changes->system-messages cofx clock-values)
|
||||
(models.message/add-system-messages cofx)))))
|
||||
|
||||
(defn- unwrap-events
|
||||
"Flatten all events, denormalizing from field"
|
||||
[all-updates]
|
||||
(mapcat
|
||||
(fn [{:keys [events from]}]
|
||||
(map #(assoc % :from from) events))
|
||||
all-updates))
|
||||
|
||||
(fx/defn handle-membership-update
|
||||
"Upsert chat and receive message if valid"
|
||||
|
@ -306,10 +378,21 @@
|
|||
sender-signature]
|
||||
(let [dev-mode? (get-in cofx [:db :account/account :dev-mode?])]
|
||||
(when (and (config/group-chats-enabled? dev-mode?)
|
||||
(valid-chat-id? chat-id (-> membership-updates first :from)))
|
||||
(let [previous-chat (get-in cofx [:db :chats chat-id])]
|
||||
(valid-chat-id? chat-id (extract-creator membership-update)))
|
||||
(let [previous-chat (get-in cofx [:db :chats chat-id])
|
||||
all-updates (clojure.set/union (set (:membership-updates previous-chat))
|
||||
(set (:membership-updates membership-update)))
|
||||
unwrapped-events (unwrap-events all-updates)
|
||||
new-group (build-group unwrapped-events)]
|
||||
(fx/merge cofx
|
||||
(update-membership previous-chat membership-update)
|
||||
(models.chat/upsert-chat {:chat-id chat-id
|
||||
:name (:name new-group)
|
||||
:is-active (get previous-chat :is-active true)
|
||||
:group-chat true
|
||||
:membership-updates (into [] all-updates)
|
||||
:admins (:admins new-group)
|
||||
:contacts (:contacts new-group)})
|
||||
(add-system-messages chat-id previous-chat new-group)
|
||||
#(when (and message
|
||||
;; don't allow anything but group messages
|
||||
(instance? protocol/Message message)
|
||||
|
|
|
@ -108,7 +108,6 @@
|
|||
{:chat-received-message/add-fx
|
||||
[(assoc (into {} this)
|
||||
:message-id (transport.utils/message-id this)
|
||||
:show? true
|
||||
:chat-id chat-id
|
||||
:from signature
|
||||
:js-obj (:js-obj cofx))]})
|
||||
|
|
|
@ -22,26 +22,6 @@
|
|||
[status-im.ui.components.colors :as colors]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(defview message-content-status []
|
||||
(letsubs [{:keys [chat-id group-id name color public-key]} [:get-current-chat]
|
||||
members [:get-current-chat-contacts]]
|
||||
(let [{:keys [status]} (if group-id
|
||||
{:status nil}
|
||||
(first members))]
|
||||
[react/view style/status-container
|
||||
[chat-icon.screen/chat-icon-message-status chat-id group-id name color false]
|
||||
[react/text {:style style/status-from
|
||||
:font :default
|
||||
:number-of-lines 1}
|
||||
(if (string/blank? name)
|
||||
(gfycat/generate-gfy public-key)
|
||||
(or (i18n/get-contact-translated chat-id :name name)
|
||||
(i18n/label :t/chat-name)))]
|
||||
(when status
|
||||
[react/text {:style style/status-text
|
||||
:font :default}
|
||||
status])])))
|
||||
|
||||
(defview message-content-command
|
||||
[command-message]
|
||||
(letsubs [id->command [:get-id->command]]
|
||||
|
@ -178,6 +158,12 @@
|
|||
:number-of-lines 5}
|
||||
text]]))
|
||||
|
||||
(defview message-content-status [{:keys [content]}]
|
||||
[react/view style/status-container
|
||||
[react/text {:style style/status-text
|
||||
:font :default}
|
||||
(cached-parse-text (:text content) nil false)]])
|
||||
|
||||
(defn text-message
|
||||
[{:keys [content timestamp-str group-chat outgoing current-public-key] :as message}]
|
||||
[message-view message
|
||||
|
@ -216,8 +202,8 @@
|
|||
[wrapper message [text-message message]])
|
||||
|
||||
(defmethod message-content constants/content-type-status
|
||||
[_ _]
|
||||
[message-content-status])
|
||||
[wrapper message]
|
||||
[wrapper message [message-content-status message]])
|
||||
|
||||
(defmethod message-content constants/content-type-command
|
||||
[wrapper message]
|
||||
|
@ -312,17 +298,18 @@
|
|||
(let [outgoing-status (or (get-in user-statuses [current-public-key :status]) :not-sent)
|
||||
delivery-status (get-in user-statuses [chat-id :status])
|
||||
status (or delivery-status outgoing-status)]
|
||||
(case status
|
||||
:sending [message-activity-indicator]
|
||||
:not-sent [message-not-sent-text chat-id message-id]
|
||||
(if (and (not outgoing)
|
||||
(:command content))
|
||||
[command-status content]
|
||||
(when last-outgoing?
|
||||
(if (= message-type :group-user-message)
|
||||
[group-message-delivery-status message]
|
||||
(if outgoing
|
||||
[text-status status])))))))
|
||||
(when (not= :system-message message-type)
|
||||
(case status
|
||||
:sending [message-activity-indicator]
|
||||
:not-sent [message-not-sent-text chat-id message-id]
|
||||
(if (and (not outgoing)
|
||||
(:command content))
|
||||
[command-status content]
|
||||
(when last-outgoing?
|
||||
(if (= message-type :group-user-message)
|
||||
[group-message-delivery-status message]
|
||||
(if outgoing
|
||||
[text-status status]))))))))
|
||||
|
||||
(defview message-author-name [from message-username]
|
||||
(letsubs [username [:get-contact-name-by-identity from]]
|
||||
|
@ -333,6 +320,7 @@
|
|||
[{:keys [last-in-group?
|
||||
display-photo?
|
||||
display-username?
|
||||
message-type
|
||||
from
|
||||
outgoing
|
||||
modal?
|
||||
|
@ -345,7 +333,7 @@
|
|||
[react/touchable-highlight {:on-press #(when-not modal? (re-frame/dispatch [:chat.ui/show-profile from]))}
|
||||
[react/view
|
||||
[photos/member-photo from]]])])
|
||||
[react/view (style/group-message-view outgoing)
|
||||
[react/view (style/group-message-view outgoing message-type)
|
||||
(when display-username?
|
||||
[message-author-name from username])
|
||||
[react/view {:style (style/timestamp-content-wrapper message)}
|
||||
|
|
|
@ -74,10 +74,12 @@
|
|||
{:flex-direction (if outgoing :row-reverse :row)})
|
||||
|
||||
(defn group-message-view
|
||||
[outgoing]
|
||||
[outgoing message-type]
|
||||
(let [align (if outgoing :flex-end :flex-start)]
|
||||
{:flex-direction :column
|
||||
:width 230
|
||||
:width (if (= :system-message message-type)
|
||||
300
|
||||
230)
|
||||
:padding-left 8
|
||||
:padding-right 8
|
||||
:align-items align}))
|
||||
|
@ -152,26 +154,12 @@
|
|||
:height 33})
|
||||
|
||||
(def status-container
|
||||
{:flex 1
|
||||
:align-self :center
|
||||
:align-items :center
|
||||
:width 249
|
||||
:padding-bottom 16})
|
||||
|
||||
(def status-image-view
|
||||
{:margin-top 20})
|
||||
|
||||
(def status-from
|
||||
{:margin-top 20
|
||||
:font-size 18
|
||||
:color colors/text})
|
||||
{:padding-horizontal 5})
|
||||
|
||||
(def status-text
|
||||
{:margin-top 10
|
||||
{:margin-top 9
|
||||
:font-size 14
|
||||
:line-height 20
|
||||
:text-align :center
|
||||
:color colors/text-gray})
|
||||
:color colors/gray})
|
||||
|
||||
(defn message-container [window-width]
|
||||
{:position :absolute
|
||||
|
|
|
@ -124,6 +124,11 @@
|
|||
:color (if outgoing colors/white colors/blue)
|
||||
:text-decoration-line :underline))
|
||||
|
||||
(def system-message-text
|
||||
{:color colors/black
|
||||
:margin-top -5
|
||||
:font-size 14})
|
||||
|
||||
(def message-container
|
||||
{:flex-direction :column
|
||||
:margin-right 16})
|
||||
|
|
|
@ -50,12 +50,6 @@
|
|||
public?
|
||||
[react/text {:style styles/public-chat-text}
|
||||
(i18n/label :t/public-chat)])]]
|
||||
#_[react/view
|
||||
[react/popup-menu
|
||||
[react/popup-menu-trigger {:text "Popup test"}]
|
||||
[react/popup-menu-options
|
||||
[react/popup-menu-option {:text "First"}]
|
||||
[react/popup-menu-option {:text "Second"}]]]]
|
||||
[react/view
|
||||
(when (and (not group-chat) (not public?))
|
||||
[react/text {:style (styles/profile-actions-text colors/black)
|
||||
|
@ -69,7 +63,10 @@
|
|||
:on-press #(re-frame/dispatch [:chat.ui/clear-history-pressed])}
|
||||
(i18n/label :t/clear-history)]
|
||||
[react/text {:style (styles/profile-actions-text colors/black)
|
||||
:on-press #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])}
|
||||
:on-press #(re-frame/dispatch [(if (and group-chat (not public?))
|
||||
:group-chats.ui/remove-chat-pressed
|
||||
:chat.ui/remove-chat-pressed)
|
||||
chat-id])}
|
||||
(i18n/label :t/delete-chat)]]]))
|
||||
|
||||
(views/defview message-author-name [{:keys [from]}]
|
||||
|
@ -155,6 +152,18 @@
|
|||
[react/view {:style {:width 40
|
||||
:margin-horizontal 16}}])
|
||||
|
||||
(views/defview system-message [text {:keys [content from first-in-group? timestamp] :as message}]
|
||||
[react/view
|
||||
[react/view {:style {:flex-direction :row :margin-top 24}}
|
||||
[member-photo from]
|
||||
[react/view {:style {:flex 1}}]
|
||||
[react/text {:style styles/message-timestamp}
|
||||
(time/timestamp->time timestamp)]]
|
||||
[react/view {:style styles/not-first-in-group-wrapper}
|
||||
[photo-placeholder]
|
||||
[react/text {:style styles/system-message-text}
|
||||
text]]])
|
||||
|
||||
(views/defview message-with-name-and-avatar [text {:keys [from first-in-group? timestamp] :as message}]
|
||||
[react/view
|
||||
(when first-in-group?
|
||||
|
@ -181,6 +190,14 @@
|
|||
[react/view {:style styles/message-command-container}
|
||||
[message/message-content-command message]]]])
|
||||
|
||||
(views/defview message-content-status [text message]
|
||||
[react/view
|
||||
[system-message text message]])
|
||||
|
||||
(defmethod message constants/content-type-status
|
||||
[text _ message]
|
||||
[message-content-status text message])
|
||||
|
||||
(defmethod message :default
|
||||
[text me? {:keys [message-id chat-id message-status user-statuses from
|
||||
current-public-key content-type outgoing type value] :as message}]
|
||||
|
|
|
@ -40,7 +40,10 @@
|
|||
(views/defview home-list-item [[home-item-id home-item]]
|
||||
(views/letsubs [swiped? [:delete-swipe-position home-item-id]]
|
||||
(let [delete-action (if (:chat-id home-item)
|
||||
:chat.ui/remove-chat
|
||||
(if (and (:group-chat home-item)
|
||||
(not (:public? home-item)))
|
||||
:group-chats.ui/remove-chat-pressed
|
||||
:chat.ui/remove-chat)
|
||||
:browser.ui/remove-browser-pressed)
|
||||
inner-item-view (if (:chat-id home-item)
|
||||
inner-item/home-list-chat-item-inner-view
|
||||
|
|
|
@ -15,17 +15,18 @@
|
|||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.utils :as utils]))
|
||||
|
||||
(defn group-chat-profile-toolbar []
|
||||
(defn group-chat-profile-toolbar [admin?]
|
||||
[toolbar/toolbar {}
|
||||
toolbar/default-nav-back
|
||||
[toolbar/content-title ""]
|
||||
[react/touchable-highlight
|
||||
{:on-press #(re-frame/dispatch [:group-chat-profile/start-editing])}
|
||||
[react/view
|
||||
[react/text {:style common.styles/label-action-text
|
||||
:uppercase? true
|
||||
:accessibility-label :edit-button}
|
||||
(i18n/label :t/edit)]]]])
|
||||
(when admin?
|
||||
[react/touchable-highlight
|
||||
{:on-press #(re-frame/dispatch [:group-chat-profile/start-editing])}
|
||||
[react/view
|
||||
[react/text {:style common.styles/label-action-text
|
||||
:uppercase? true
|
||||
:accessibility-label :edit-button}
|
||||
(i18n/label :t/edit)]]])])
|
||||
|
||||
(defn group-chat-profile-edit-toolbar []
|
||||
[toolbar/toolbar {}
|
||||
|
@ -95,7 +96,7 @@
|
|||
[status-bar/status-bar]
|
||||
(if editing?
|
||||
[group-chat-profile-edit-toolbar]
|
||||
[group-chat-profile-toolbar])
|
||||
[group-chat-profile-toolbar admin?])
|
||||
[react/scroll-view
|
||||
[react/view profile.components.styles/profile-form
|
||||
[profile.components/profile-header
|
||||
|
|
|
@ -22,20 +22,17 @@
|
|||
new-messages [{:message-id 1
|
||||
:content "b"
|
||||
:clock-value 1
|
||||
:timestamp 1
|
||||
:show? false}
|
||||
:timestamp 1}
|
||||
{:message-id 2
|
||||
:content "c"
|
||||
:clock-value 2
|
||||
:timestamp 2
|
||||
:show? true}
|
||||
:timestamp 2}
|
||||
{:message-id 3
|
||||
:content "d"
|
||||
:clock-value 3
|
||||
:timestamp 3
|
||||
:show? true}]]
|
||||
(testing "New messages are grouped/sorted correctly, hidden messages are not grouped"
|
||||
(is (= '(2 3)
|
||||
:timestamp 3}]]
|
||||
(testing "New messages are grouped/sorted correctly"
|
||||
(is (= '(1 2 3)
|
||||
(map :message-id
|
||||
(-> (get-in (loading/group-chat-messages cofx "chat-id" new-messages)
|
||||
[:db :chats "chat-id" :message-groups])
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
(let [actual (message/receive-many {:db db}
|
||||
[{:from "me"
|
||||
:message-type :user-message
|
||||
:timestamp 0
|
||||
:message-id "id"
|
||||
:chat-id "chat-id"
|
||||
:content "b"
|
||||
|
|
|
@ -107,7 +107,18 @@
|
|||
(is (:last-in-group? actual-m1))
|
||||
(is (:last-in-group? actual-m2))
|
||||
(is (:last-in-group? actual-m3))
|
||||
(is (not (:last-in-group? actual-m4)))))))
|
||||
(is (not (:last-in-group? actual-m4))))))
|
||||
(testing "system-messages"
|
||||
(let [m1 {:from "system"
|
||||
:message-type :system-message
|
||||
:datemark "a"
|
||||
:outgoing false}
|
||||
messages [m1]
|
||||
[actual-m1] (s/messages-stream messages)]
|
||||
(testing "it does display the photo"
|
||||
(is (:display-photo? actual-m1))
|
||||
(testing "it does not display the username"
|
||||
(is (not (:display-username? actual-m1))))))))
|
||||
|
||||
(deftest active-chats-test
|
||||
(let [active-chat-1 {:is-active true :chat-id 1}
|
||||
|
|
|
@ -26,13 +26,13 @@
|
|||
:members [member-2 member-3]}]}]})
|
||||
|
||||
(deftest get-last-clock-value-test
|
||||
(is (= 3 (group-chats/get-last-clock-value {:db {:chats {chat-id initial-message}}} chat-id))))
|
||||
(is (= 3 (group-chats/get-last-clock-value {:db {:chats {chat-id {:last-clock-value 3}}}} chat-id))))
|
||||
|
||||
(deftest handle-group-membership-update
|
||||
(deftest handle-group-membership-update-test
|
||||
(with-redefs [config/group-chats-enabled? (constantly true)]
|
||||
(testing "a brand new chat"
|
||||
(let [actual (->
|
||||
(group-chats/handle-membership-update {:db {}} initial-message admin)
|
||||
(group-chats/handle-membership-update {:now 0 :db {}} initial-message admin)
|
||||
:db
|
||||
:chats
|
||||
(get chat-id))]
|
||||
|
@ -52,12 +52,19 @@
|
|||
(:membership-updates actual))))
|
||||
(testing "it sets the right admins"
|
||||
(is (= #{admin}
|
||||
(:admins actual))))))
|
||||
(:admins actual))))
|
||||
(testing "it adds a system message"
|
||||
(is (= 3 (count (:messages actual)))))
|
||||
(testing "it adds the right text"
|
||||
(is (= ["group-chat-created"
|
||||
"group-chat-member-added"
|
||||
"group-chat-member-added"]
|
||||
(map (comp :text :content) (sort-by :clock-value (vals (:messages actual)))))))))
|
||||
(testing "a chat with the wrong id"
|
||||
(let [bad-chat-id (str random-id member-2)
|
||||
actual (->
|
||||
(group-chats/handle-membership-update
|
||||
{:db {}}
|
||||
{:now 0 :db {}}
|
||||
(assoc initial-message :chat-id bad-chat-id)
|
||||
admin)
|
||||
:db
|
||||
|
@ -66,18 +73,15 @@
|
|||
(testing "it does not create a chat"
|
||||
(is (not actual)))))
|
||||
(testing "an already existing chat"
|
||||
(let [cofx {:db {:chats {chat-id {:admins #{admin}
|
||||
:name "chat-name"
|
||||
:chat-id chat-id
|
||||
:is-active true
|
||||
:group-chat true
|
||||
:contacts #{member-1 member-2 member-3}
|
||||
:membership-updates (:membership-updates initial-message)}}}}]
|
||||
(let [cofx (assoc
|
||||
(group-chats/handle-membership-update {:now 0 :db {}} initial-message admin)
|
||||
:now 0)]
|
||||
(testing "the message has already been received"
|
||||
(let [actual (group-chats/handle-membership-update cofx initial-message admin)]
|
||||
(testing "it noops"
|
||||
(is (= (get-in actual [:db :chats chat-id])
|
||||
(get-in cofx [:db :chats chat-id]))))))
|
||||
(is (=
|
||||
(get-in cofx [:db :chats chat-id])
|
||||
(get-in actual [:db :chats chat-id]))))))
|
||||
(testing "a new message comes in"
|
||||
(let [actual (group-chats/handle-membership-update cofx
|
||||
{:chat-id chat-id
|
||||
|
@ -97,7 +101,10 @@
|
|||
:member member-3}
|
||||
{:type "members-added"
|
||||
:clock-value 12
|
||||
:members [member-4]}]}]}
|
||||
:members [member-4]}
|
||||
{:type "name-changed"
|
||||
:clock-value 13
|
||||
:name "new-name"}]}]}
|
||||
member-3)
|
||||
actual-chat (get-in actual [:db :chats chat-id])]
|
||||
(testing "the chat is updated"
|
||||
|
@ -105,7 +112,19 @@
|
|||
(testing "admins are updated"
|
||||
(is (= #{member-2} (:admins actual-chat))))
|
||||
(testing "members are updated"
|
||||
(is (= #{member-1 member-2 member-4} (:contacts actual-chat))))))))))
|
||||
(is (= #{member-1 member-2 member-4} (:contacts actual-chat))))
|
||||
(testing "the name is updated"
|
||||
(is (= "new-name" (:name actual-chat))))
|
||||
(testing "it adds a system message"
|
||||
(is (= 6 (count (:messages actual-chat)))))
|
||||
(testing "it sets the right text"
|
||||
(is (= ["group-chat-created"
|
||||
"group-chat-member-added"
|
||||
"group-chat-member-added"
|
||||
"group-chat-member-added"
|
||||
"group-chat-member-removed"
|
||||
"group-chat-name-changed"]
|
||||
(map (comp :text :content) (sort-by :clock-value (vals (:messages actual-chat)))))))))))))
|
||||
|
||||
(deftest build-group-test
|
||||
(testing "only adds"
|
||||
|
@ -126,6 +145,10 @@
|
|||
:from "2"
|
||||
:members ["3"]}]
|
||||
expected {:name "chat-name"
|
||||
:created-at 0
|
||||
"2" {:added 1
|
||||
:admin-added 2}
|
||||
"3" {:added 3}
|
||||
:admins #{"1" "2"}
|
||||
:contacts #{"1" "2" "3"}}]
|
||||
(is (= expected (group-chats/build-group events)))))
|
||||
|
@ -151,6 +174,11 @@
|
|||
:from "2"
|
||||
:member "2"}]
|
||||
expected {:name "chat-name"
|
||||
:created-at 0
|
||||
"2" {:added 1
|
||||
:admin-added 2
|
||||
:admin-removed 3
|
||||
:removed 4}
|
||||
:admins #{"1"}
|
||||
:contacts #{"1"}}]
|
||||
(is (= expected (group-chats/build-group events)))))
|
||||
|
@ -172,6 +200,11 @@
|
|||
:from "2"
|
||||
:name "new-name"}]
|
||||
expected {:name "new-name"
|
||||
:created-at 0
|
||||
:name-changed-by "2"
|
||||
:name-changed-at 3
|
||||
"2" {:added 1
|
||||
:admin-added 2}
|
||||
:admins #{"1" "2"}
|
||||
:contacts #{"1" "2"}}]
|
||||
(is (= expected (group-chats/build-group events)))))
|
||||
|
@ -205,6 +238,10 @@
|
|||
:from "1"
|
||||
:member "2"}]
|
||||
expected {:name "chat-name"
|
||||
:created-at 0
|
||||
"2" {:added 2
|
||||
:admin-added 3}
|
||||
"3" {:added 4}
|
||||
:admins #{"1" "2"}
|
||||
:contacts #{"1" "2" "3"}}]
|
||||
(is (= expected (group-chats/build-group events)))))
|
||||
|
@ -226,6 +263,10 @@
|
|||
:from "2"
|
||||
:members ["3"]}]
|
||||
expected {:name "chat-name"
|
||||
:created-at 0
|
||||
"2" {:added 1
|
||||
:admin-added 2}
|
||||
"3" {:added 3}
|
||||
:admins #{"1" "2"}
|
||||
:contacts #{"1" "2" "3"}}]
|
||||
(is (= expected (group-chats/build-group events))))))
|
||||
|
@ -343,6 +384,7 @@
|
|||
(let [cofx {:db {:chats {chat-id {:admins #{admin}
|
||||
:name "chat-name"
|
||||
:chat-id chat-id
|
||||
:last-clock-value 3
|
||||
:is-active true
|
||||
:group-chat true
|
||||
:contacts #{member-1 member-2 member-3}
|
||||
|
@ -367,7 +409,8 @@
|
|||
(let [cofx {:db {:current-chat-id chat-id
|
||||
:selected-participants ["new-member"]
|
||||
:current-public-key "me"
|
||||
:chats {chat-id {:membership-updates [{:events [{:clock-value 1}]}]}}}}]
|
||||
:chats {chat-id {:last-clock-value 1
|
||||
:membership-updates [{:events [{:clock-value 1}]}]}}}}]
|
||||
(is (= {:chat-id chat-id
|
||||
:from "me"
|
||||
:events [{:type "members-added"
|
||||
|
@ -380,6 +423,7 @@
|
|||
(testing "remove-member"
|
||||
(let [cofx {:db {:current-public-key "me"
|
||||
:chats {chat-id {:admins #{"me"}
|
||||
:last-clock-value 1
|
||||
:contacts #{"member"}
|
||||
:membership-updates [{:events [{:clock-value 1}]}]}}}}]
|
||||
(is (= {:chat-id chat-id
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
"name-placeholder": "Display name",
|
||||
"find": "Find",
|
||||
"close-app-title": "Warning!",
|
||||
"group-chat-created": "*{{member}}* created the group *{{name}}*",
|
||||
"group-chat-name-changed": "*{{member}}* changed the group's name to *{{name}}*",
|
||||
"group-chat-member-added": "*{{member}}* joined the group",
|
||||
"group-chat-member-removed": "*{{member}}* left the group",
|
||||
"agree-by-continuing": "By continuing you agree\n to our ",
|
||||
"wallet-advanced": "Advanced",
|
||||
"currency-display-name-sos": "Somalia Shilling",
|
||||
|
|
Loading…
Reference in New Issue