Add joining of group chats
Members will now have to explicitly join a group chat to start receiving messages from it. Messages are still sent to users who have not joined for backward compatibility. Group updates are unaffected.
This commit is contained in:
parent
a52f3d4d08
commit
881691fbc3
|
@ -19,15 +19,27 @@
|
|||
[status-im.utils.priority-map :refer [empty-message-map]]
|
||||
[status-im.utils.utils :as utils]))
|
||||
|
||||
(defn multi-user-chat? [cofx chat-id]
|
||||
(get-in cofx [:db :chats chat-id :group-chat]))
|
||||
(defn- get-chat [cofx chat-id]
|
||||
(get-in cofx [:db :chats chat-id]))
|
||||
|
||||
(defn group-chat? [cofx chat-id]
|
||||
(and (multi-user-chat? cofx chat-id)
|
||||
(not (get-in cofx [:db :chats chat-id :public?]))))
|
||||
(defn multi-user-chat?
|
||||
([chat]
|
||||
(:group-chat chat))
|
||||
([cofx chat-id]
|
||||
(multi-user-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn public-chat? [cofx chat-id]
|
||||
(get-in cofx [:db :chats chat-id :public?]))
|
||||
(defn public-chat?
|
||||
([chat]
|
||||
(:public? chat))
|
||||
([cofx chat-id]
|
||||
(public-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn group-chat?
|
||||
([chat]
|
||||
(and (multi-user-chat? chat)
|
||||
(not (public-chat? chat))))
|
||||
([cofx chat-id]
|
||||
(group-chat? (get-chat cofx chat-id))))
|
||||
|
||||
(defn set-chat-ui-props
|
||||
"Updates ui-props in active chat by merging provided kvs into them"
|
||||
|
|
|
@ -252,7 +252,7 @@
|
|||
(cond
|
||||
(and (= :group-user-message message-type)
|
||||
(and (get-in cofx [:db :chats chat-id :contacts from])
|
||||
(get-in cofx [:db :chats chat-id :contacts (accounts.db/current-public-key cofx)]))) chat-id
|
||||
(get-in cofx [:db :chats chat-id :members-joined (accounts.db/current-public-key cofx)]))) chat-id
|
||||
(and (= :public-group-user-message message-type)
|
||||
(get-in cofx [:db :chats chat-id :public?])) chat-id
|
||||
(and (= :user-message message-type)
|
||||
|
|
|
@ -1254,6 +1254,11 @@
|
|||
(fn [cofx [_ chat-id]]
|
||||
(group-chats/remove cofx chat-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:group-chats.ui/join-pressed
|
||||
(fn [cofx [_ chat-id]]
|
||||
(group-chats/join-chat cofx chat-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:group-chats.callback/sign-success
|
||||
[(re-frame/inject-cofx :random-guid-generator)]
|
||||
|
|
|
@ -84,6 +84,8 @@
|
|||
"name-changed" (and (admins from)
|
||||
(not (string/blank? (:name new-event))))
|
||||
"members-added" (admins from)
|
||||
"member-joined" (and (contacts member)
|
||||
(= from member))
|
||||
"admins-added" (and (admins from)
|
||||
(clojure.set/subset? members contacts))
|
||||
"member-removed" (or
|
||||
|
@ -168,6 +170,11 @@
|
|||
:clock-value (utils.clocks/send last-clock-value)
|
||||
:members members})
|
||||
|
||||
(defn- member-joined-event [last-clock-value member]
|
||||
{:type "member-joined"
|
||||
:clock-value (utils.clocks/send last-clock-value)
|
||||
:member member})
|
||||
|
||||
(fx/defn create
|
||||
"Format group update message and sign membership"
|
||||
[{:keys [db random-guid-generator] :as cofx} group-name]
|
||||
|
@ -202,6 +209,20 @@
|
|||
:from my-public-key
|
||||
:events [remove-event]}})))
|
||||
|
||||
(fx/defn join-chat
|
||||
"Format group update message and sign membership"
|
||||
[{:keys [db] :as cofx} chat-id]
|
||||
(let [my-public-key (accounts.db/current-public-key cofx)
|
||||
last-clock-value (get-last-clock-value cofx chat-id)
|
||||
chat (get-in cofx [:db :chats chat-id])
|
||||
event (member-joined-event last-clock-value my-public-key)]
|
||||
(when (valid-event? chat (assoc event
|
||||
:from
|
||||
my-public-key))
|
||||
{:group-chats/sign-membership {:chat-id chat-id
|
||||
:from my-public-key
|
||||
:events [event]}})))
|
||||
|
||||
(fx/defn make-admin
|
||||
"Format group update with make admin message and sign membership"
|
||||
[{:keys [db] :as cofx} chat-id member]
|
||||
|
@ -283,8 +304,9 @@
|
|||
(case type
|
||||
"chat-created" {:name name
|
||||
:created-at clock-value
|
||||
:admins #{from}
|
||||
:contacts #{from}}
|
||||
:admins #{from}
|
||||
:members-joined #{from}
|
||||
:contacts #{from}}
|
||||
"name-changed" (assoc group
|
||||
:name name
|
||||
:name-changed-by from
|
||||
|
@ -292,12 +314,16 @@
|
|||
"members-added" (as-> group $
|
||||
(update $ :contacts clojure.set/union (set members))
|
||||
(reduce (fn [acc member] (assoc-in acc [member :added] clock-value)) $ members))
|
||||
"member-joined" (-> group
|
||||
(update :members-joined conj member)
|
||||
(assoc-in [member :joined] clock-value))
|
||||
"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)
|
||||
(update :admins disj member)
|
||||
(update :members-joined disj member)
|
||||
(assoc-in [member :removed] clock-value))
|
||||
"admin-removed" (-> group
|
||||
(update :admins disj member)
|
||||
|
@ -312,6 +338,7 @@
|
|||
(reduce
|
||||
process-event
|
||||
{:admins #{}
|
||||
:members-joined #{}
|
||||
:contacts #{}})))
|
||||
|
||||
(defn membership-changes->system-messages [cofx
|
||||
|
@ -320,6 +347,7 @@
|
|||
chat-name
|
||||
creator
|
||||
members-added
|
||||
members-joined
|
||||
admins-added
|
||||
name-changed?
|
||||
members-removed]}]
|
||||
|
@ -337,6 +365,9 @@
|
|||
contacts-added (map
|
||||
get-contact
|
||||
(disj members-added creator))
|
||||
contacts-joined (map
|
||||
get-contact
|
||||
(disj members-joined creator))
|
||||
contacts-removed (map
|
||||
get-contact
|
||||
members-removed)]
|
||||
|
@ -356,6 +387,11 @@
|
|||
(i18n/label :t/group-chat-member-added {:member (:name %)})
|
||||
(get-in clock-values [(:public-key %) :added]))
|
||||
contacts-added))
|
||||
(seq members-joined) (concat (map #(format-message
|
||||
%
|
||||
(i18n/label :t/group-chat-member-joined {:member (:name %)})
|
||||
(get-in clock-values [(:public-key %) :joined]))
|
||||
contacts-joined))
|
||||
(seq admins-added) (concat (map #(format-message
|
||||
%
|
||||
(i18n/label :t/group-chat-admin-added {:member (:name %)})
|
||||
|
@ -373,6 +409,7 @@
|
|||
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-joined (clojure.set/difference (:members-joined current-chat) (:members-joined previous-chat))
|
||||
members-removed (clojure.set/difference (:contacts previous-chat) (:contacts current-chat))
|
||||
admins-added (clojure.set/difference (:admins current-chat) (:admins previous-chat))
|
||||
membership-changes (cond-> {:chat-id chat-id
|
||||
|
@ -380,12 +417,14 @@
|
|||
:chat-name (:name current-chat)
|
||||
:admins-added admins-added
|
||||
:members-added members-added
|
||||
:members-joined members-joined
|
||||
:members-removed members-removed}
|
||||
(nil? previous-chat)
|
||||
(assoc :creator (extract-creator current-chat)))]
|
||||
(when (or name-changed?
|
||||
(seq admins-added)
|
||||
(seq members-added)
|
||||
(seq members-joined)
|
||||
(seq members-removed))
|
||||
(->> membership-changes
|
||||
(membership-changes->system-messages cofx clock-values)
|
||||
|
@ -399,6 +438,21 @@
|
|||
(map #(assoc % :from from) events))
|
||||
all-updates))
|
||||
|
||||
(defn joined? [my-public-key {:keys [members-joined]}]
|
||||
(contains? members-joined my-public-key))
|
||||
|
||||
(defn invited? [my-public-key {:keys [contacts]}]
|
||||
(contains? contacts my-public-key))
|
||||
|
||||
(defn get-inviter-pk [my-public-key {:keys [membership-updates] :as chat}]
|
||||
(->> membership-updates
|
||||
unwrap-events
|
||||
(keep (fn [{:keys [from type members]}]
|
||||
(when (and (= type "members-added")
|
||||
(contains? members my-public-key))
|
||||
from)))
|
||||
last))
|
||||
|
||||
(fx/defn handle-membership-update
|
||||
"Upsert chat and receive message if valid"
|
||||
;; Care needs to be taken here as chat-id is not coming from a whisper filter
|
||||
|
@ -423,6 +477,7 @@
|
|||
:group-chat true
|
||||
:membership-updates (into [] all-updates)
|
||||
:admins (:admins new-group)
|
||||
:members-joined (:members-joined new-group)
|
||||
:contacts (:contacts new-group)})
|
||||
(add-system-messages chat-id previous-chat new-group)
|
||||
#(when (and message
|
||||
|
|
|
@ -234,3 +234,6 @@
|
|||
|
||||
(def empty-chat-text-name
|
||||
{:color colors/black})
|
||||
|
||||
(def join-button
|
||||
{:margin-bottom 5})
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
[re-frame.core :as re-frame]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.contact.core :as models.contact]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im.group-chats.core :as models.group-chats]
|
||||
[status-im.ui.screens.chat.styles.main :as style]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.ui.screens.chat.input.input :as input]
|
||||
|
@ -14,6 +16,7 @@
|
|||
[status-im.ui.screens.chat.message.datemark :as message-datemark]
|
||||
[status-im.ui.screens.chat.toolbar-content :as toolbar-content]
|
||||
[status-im.ui.components.animation :as animation]
|
||||
[status-im.ui.components.button.view :as buttons]
|
||||
[status-im.ui.components.list.views :as list]
|
||||
[status-im.ui.components.list-selection :as list-selection]
|
||||
[status-im.ui.components.react :as react]
|
||||
|
@ -114,7 +117,21 @@
|
|||
[react/text {:style style/empty-chat-text-name} (:name contact)]]
|
||||
(i18n/label :t/empty-chat-description))]])))
|
||||
|
||||
(defview messages-view [chat group-chat modal?]
|
||||
(defn join-chat-button [chat-id]
|
||||
[buttons/primary-button {:style style/join-button
|
||||
:on-press #(re-frame/dispatch [:group-chats.ui/join-pressed chat-id])}
|
||||
(i18n/label :t/join-group-chat)])
|
||||
|
||||
(defview group-chat-join-section [my-public-key {:keys [name chat-id] :as chat}]
|
||||
(letsubs [contact [:contacts/contact-by-identity (models.group-chats/get-inviter-pk my-public-key chat)]]
|
||||
[react/view style/empty-chat-container
|
||||
[join-chat-button chat-id]
|
||||
[react/text {:style style/empty-chat-text}
|
||||
[react/text style/empty-chat-container-one-to-one
|
||||
(i18n/label :t/join-group-chat-description {:username (:name contact)
|
||||
:group-name name})]]]))
|
||||
|
||||
(defview messages-view [{:keys [group-chat] :as chat} modal?]
|
||||
(letsubs [messages [:chats/current-chat-messages-stream]
|
||||
current-public-key [:account/public-key]]
|
||||
{:component-did-mount
|
||||
|
@ -124,9 +141,18 @@
|
|||
(re-frame/dispatch [:chat.ui/set-chat-ui-props
|
||||
{:messages-focused? true
|
||||
:input-focused? false}]))}
|
||||
(if (and (empty? messages)
|
||||
(:messages-initialized? chat))
|
||||
(cond
|
||||
|
||||
(and (models.chat/group-chat? chat)
|
||||
(models.group-chats/invited? current-public-key chat)
|
||||
(not (models.group-chats/joined? current-public-key chat)))
|
||||
[group-chat-join-section current-public-key chat]
|
||||
|
||||
(and (empty? messages)
|
||||
(:messages-initialized? chat))
|
||||
[empty-chat-container chat]
|
||||
|
||||
:else
|
||||
[list/flat-list {:data messages
|
||||
:key-fn #(or (:message-id %) (:value %))
|
||||
:render-fn (fn [message]
|
||||
|
@ -139,15 +165,20 @@
|
|||
:enableEmptySections true
|
||||
:keyboardShouldPersistTaps :handled}])))
|
||||
|
||||
(defview messages-view-wrapper [group-chat modal?]
|
||||
(defview messages-view-wrapper [modal?]
|
||||
(letsubs [chat [:chats/current-chat]]
|
||||
[messages-view chat group-chat modal?]))
|
||||
[messages-view chat modal?]))
|
||||
|
||||
(defn show-input-container? [my-public-key current-chat]
|
||||
(or (not (models.chat/group-chat? current-chat))
|
||||
(models.group-chats/joined? my-public-key current-chat)))
|
||||
|
||||
(defview chat-root [modal?]
|
||||
(letsubs [{:keys [group-chat public?]} [:chats/current-chat]
|
||||
show-bottom-info? [:chats/current-chat-ui-prop :show-bottom-info?]
|
||||
show-message-options? [:chats/current-chat-ui-prop :show-message-options?]
|
||||
current-view [:get :view-id]]
|
||||
(letsubs [{:keys [public?] :as current-chat} [:chats/current-chat]
|
||||
my-public-key [:account/public-key]
|
||||
show-bottom-info? [:chats/current-chat-ui-prop :show-bottom-info?]
|
||||
show-message-options? [:chats/current-chat-ui-prop :show-message-options?]
|
||||
current-view [:get :view-id]]
|
||||
;; this scroll-view is a hack that allows us to use on-blur and on-focus on Android
|
||||
;; more details here: https://github.com/facebook/react-native/issues/11071
|
||||
[react/scroll-view {:scroll-enabled false
|
||||
|
@ -160,9 +191,10 @@
|
|||
[chat-toolbar public? modal?]
|
||||
(if (or (= :chat current-view) modal?)
|
||||
[messages-view-animation
|
||||
[messages-view-wrapper group-chat modal?]]
|
||||
[messages-view-wrapper modal?]]
|
||||
[react/view style/message-view-preview])
|
||||
[input/container]
|
||||
(when (show-input-container? my-public-key current-chat)
|
||||
[input/container])
|
||||
(when show-bottom-info?
|
||||
[bottom-info/bottom-info-view])
|
||||
(when show-message-options?
|
||||
|
|
|
@ -66,11 +66,12 @@
|
|||
(is (= :sent status)))))))
|
||||
|
||||
(deftest receive-group-chats
|
||||
(let [cofx {:db {:chats {"chat-id" {:contacts #{"present" "a"}}}
|
||||
(let [cofx {:db {:chats {"chat-id" {:contacts #{"present"}
|
||||
:members-joined #{"a"}}}
|
||||
:account/account {:public-key "a"}
|
||||
:current-chat-id "chat-id"
|
||||
:view-id :chat}}
|
||||
cofx-without-member (update-in cofx [:db :chats "chat-id" :contacts] disj "a")
|
||||
cofx-without-member (update-in cofx [:db :chats "chat-id" :members-joined] disj "a")
|
||||
valid-message {:chat-id "chat-id"
|
||||
:from "present"
|
||||
:message-type :group-user-message
|
||||
|
|
|
@ -146,13 +146,19 @@
|
|||
{:type "members-added"
|
||||
:clock-value 3
|
||||
:from "2"
|
||||
:members ["3"]}]
|
||||
:members ["3"]}
|
||||
{:type "member-joined"
|
||||
:clock-value 4
|
||||
:from "3"
|
||||
:member "3"}]
|
||||
expected {:name "chat-name"
|
||||
:created-at 0
|
||||
"2" {:added 1
|
||||
:admin-added 2}
|
||||
"3" {:added 3}
|
||||
"3" {:added 3
|
||||
:joined 4}
|
||||
:admins #{"1" "2"}
|
||||
:members-joined #{"1" "3"}
|
||||
:contacts #{"1" "2" "3"}}]
|
||||
(is (= expected (group-chats/build-group events)))))
|
||||
(testing "adds and removes"
|
||||
|
@ -164,25 +170,31 @@
|
|||
:clock-value 1
|
||||
:from "1"
|
||||
:members ["2"]}
|
||||
{:type "admins-added"
|
||||
:clock-value 2
|
||||
:from "1"
|
||||
:members ["2"]}
|
||||
{:type "admin-removed"
|
||||
{:type "member-joined"
|
||||
:clock-value 3
|
||||
:from "2"
|
||||
:member "2"}
|
||||
{:type "member-removed"
|
||||
{:type "admins-added"
|
||||
:clock-value 4
|
||||
:from "1"
|
||||
:members ["2"]}
|
||||
{:type "admin-removed"
|
||||
:clock-value 5
|
||||
:from "2"
|
||||
:member "2"}
|
||||
{:type "member-removed"
|
||||
:clock-value 6
|
||||
:from "2"
|
||||
:member "2"}]
|
||||
expected {:name "chat-name"
|
||||
:created-at 0
|
||||
"2" {:added 1
|
||||
:admin-added 2
|
||||
:admin-removed 3
|
||||
:removed 4}
|
||||
:joined 3
|
||||
:admin-added 4
|
||||
:admin-removed 5
|
||||
:removed 6}
|
||||
:admins #{"1"}
|
||||
:members-joined #{"1"}
|
||||
:contacts #{"1"}}]
|
||||
(is (= expected (group-chats/build-group events)))))
|
||||
(testing "an admin removing themselves"
|
||||
|
@ -204,6 +216,7 @@
|
|||
:member "2"}]
|
||||
expected {:name "chat-name"
|
||||
:created-at 0
|
||||
:members-joined #{"1"}
|
||||
"2" {:added 1
|
||||
:admin-added 2
|
||||
:removed 3}
|
||||
|
@ -229,6 +242,7 @@
|
|||
:name "new-name"}]
|
||||
expected {:name "new-name"
|
||||
:created-at 0
|
||||
:members-joined #{"1"}
|
||||
:name-changed-by "2"
|
||||
:name-changed-at 3
|
||||
"2" {:added 1
|
||||
|
@ -249,6 +263,10 @@
|
|||
:clock-value 2
|
||||
:from "1"
|
||||
:members ["2"]}
|
||||
{:type "member-joined" ; non-invited user joining
|
||||
:clock-value 2
|
||||
:from "non-invited"
|
||||
:member "non-invited"}
|
||||
{:type "admins-added"
|
||||
:clock-value 3
|
||||
:from "1"
|
||||
|
@ -261,15 +279,40 @@
|
|||
:clock-value 5
|
||||
:from "1"
|
||||
:member "2"}
|
||||
{:type "member-joined"
|
||||
:clock-value 5
|
||||
:from "2"
|
||||
:member "2"}
|
||||
{:type "member-removed" ; can't remove an admin from the group
|
||||
:clock-value 6
|
||||
:from "1"
|
||||
:member "2"}]
|
||||
:member "2"}
|
||||
{:type "members-added"
|
||||
:clock-value 7
|
||||
:from "2"
|
||||
:members ["4"]}
|
||||
{:type "member-joined"
|
||||
:clock-value 8
|
||||
:from "4"
|
||||
:member "4"}
|
||||
{:type "member-removed"
|
||||
:clock-value 9
|
||||
:from "1"
|
||||
:member "4"}
|
||||
{:type "member-joined" ; join after being removed
|
||||
:clock-value 10
|
||||
:from "4"
|
||||
:member "4"}]
|
||||
expected {:name "chat-name"
|
||||
:members-joined #{"1" "2"}
|
||||
:created-at 0
|
||||
"2" {:added 2
|
||||
:admin-added 3}
|
||||
:admin-added 3
|
||||
:joined 5}
|
||||
"3" {:added 4}
|
||||
"4" {:added 7
|
||||
:joined 8
|
||||
:removed 9}
|
||||
:admins #{"1" "2"}
|
||||
:contacts #{"1" "2" "3"}}]
|
||||
(is (= expected (group-chats/build-group events)))))
|
||||
|
@ -292,6 +335,7 @@
|
|||
:members ["3"]}]
|
||||
expected {:name "chat-name"
|
||||
:created-at 0
|
||||
:members-joined #{"1"}
|
||||
"2" {:added 1
|
||||
:admin-added 2}
|
||||
"3" {:added 3}
|
||||
|
|
|
@ -38,10 +38,13 @@
|
|||
"other": "You can select {{count}} more participants"
|
||||
},
|
||||
"no-more-participants-available": "You can't add anymore participants",
|
||||
"join-group-chat-description": "{{username}} invited you to join the group {{group-name}}",
|
||||
"join-group-chat": "Join chat",
|
||||
"group-chat-created": "*{{member}}* created the group *{{name}}*",
|
||||
"group-chat-admin": "Admin",
|
||||
"group-chat-name-changed": "*{{member}}* changed the group's name to *{{name}}*",
|
||||
"group-chat-member-added": "*{{member}}* joined the group",
|
||||
"group-chat-member-added": "*{{member}}* has been invited",
|
||||
"group-chat-member-joined": "*{{member}}* has joined the group",
|
||||
"group-chat-member-removed": "*{{member}}* left the group",
|
||||
"group-chat-admin-added": "*{{member}}* has been made admin",
|
||||
"group-chat-no-contacts": "You don't have any contacts yet.\nInvite your friends to start chatting",
|
||||
|
|
Loading…
Reference in New Issue