From 52ae2c2bfeb5023ac325eaea4f6a9c322d27c976 Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Tue, 15 Jan 2019 09:35:06 +0100 Subject: [PATCH] Style header group-chats Signed-off-by: Andrea Maria Piana --- src/status_im/chat/db.cljs | 6 +- src/status_im/chat/models/message.cljs | 5 +- .../realm/schemas/account/chat.cljs | 5 + .../realm/schemas/account/core.cljs | 19 +- .../realm/schemas/account/migrations.cljs | 7 + src/status_im/group_chats/core.cljs | 68 ++++--- src/status_im/node/core.cljs | 7 +- .../ui/components/chat_icon/screen.cljs | 20 +- src/status_im/ui/screens/add_new/views.cljs | 17 +- .../ui/screens/chat/styles/main.cljs | 31 ++- src/status_im/ui/screens/chat/views.cljs | 34 +++- src/status_im/ui/screens/desktop/views.cljs | 6 +- .../ui/screens/group/add_contacts/views.cljs | 131 ------------ src/status_im/ui/screens/group/styles.cljs | 19 ++ src/status_im/ui/screens/group/views.cljs | 187 +++++++++++++++--- src/status_im/ui/screens/views.cljs | 6 +- src/status_im/utils/config.cljs | 2 +- test/cljs/status_im/test/node/core.cljs | 15 +- translations/en.json | 5 +- 19 files changed, 331 insertions(+), 259 deletions(-) delete mode 100644 src/status_im/ui/screens/group/add_contacts/views.cljs diff --git a/src/status_im/chat/db.cljs b/src/status_im/chat/db.cljs index 57ab151a50..f2754e00cf 100644 --- a/src/status_im/chat/db.cljs +++ b/src/status_im/chat/db.cljs @@ -21,11 +21,7 @@ (defn active-chats [contacts chats {:keys [dev-mode?]}] (reduce (fn [acc [chat-id {:keys [group-chat public? is-active] :as chat}]] - (if (and is-active - ;; not a group chat - (or (not (and group-chat (not public?))) - ;; if it's a group chat - utils.config/group-chats-enabled?)) + (if is-active (assoc acc chat-id (if-let [contact (get contacts chat-id)] (-> chat (assoc :name (:name contact)) diff --git a/src/status_im/chat/models/message.cljs b/src/status_im/chat/models/message.cljs index 4252e275ec..7886cc0153 100644 --- a/src/status_im/chat/models/message.cljs +++ b/src/status_im/chat/models/message.cljs @@ -242,7 +242,10 @@ (cond (and (= :group-user-message message-type) (and (get-in cofx [:db :chats chat-id :contacts from]) - (get-in cofx [:db :chats chat-id :members-joined (accounts.db/current-public-key cofx)]))) chat-id + ;; Version 0 does not have a concept of joining, so any message is ok + ;; otherwise check we joined + (or (= 0 (get-in cofx [:db :chats chat-id :group-chat-local-version])) + (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) diff --git a/src/status_im/data_store/realm/schemas/account/chat.cljs b/src/status_im/data_store/realm/schemas/account/chat.cljs index fefe5fdfb4..acd4418709 100644 --- a/src/status_im/data_store/realm/schemas/account/chat.cljs +++ b/src/status_im/data_store/realm/schemas/account/chat.cljs @@ -246,3 +246,8 @@ (def v13 (update v12 :properties assoc :members-joined {:type "string[]"})) + +(def v14 + (update v13 :properties assoc + :group-chat-local-version {:type :int + :optional true})) diff --git a/src/status_im/data_store/realm/schemas/account/core.cljs b/src/status_im/data_store/realm/schemas/account/core.cljs index b79431a247..bf35ecda20 100644 --- a/src/status_im/data_store/realm/schemas/account/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/core.cljs @@ -371,6 +371,20 @@ dapp-permissions/v9 contact-recovery/v1]) +(def v34 [chat/v14 + transport/v7 + contact/v3 + message/v9 + mailserver/v11 + mailserver-topic/v1 + user-status/v2 + membership-update/v1 + installation/v3 + local-storage/v1 + browser/v8 + dapp-permissions/v9 + contact-recovery/v1]) + ;; put schemas ordered by version (def schemas [{:schema v1 :schemaVersion 1 @@ -470,4 +484,7 @@ :migration (constantly nil)} {:schema v33 :schemaVersion 33 - :migration (constantly nil)}]) \ No newline at end of file + :migration (constantly nil)} + {:schema v34 + :schemaVersion 34 + :migration migrations/v34}]) diff --git a/src/status_im/data_store/realm/schemas/account/migrations.cljs b/src/status_im/data_store/realm/schemas/account/migrations.cljs index 1d286148c5..ff8cf804d2 100644 --- a/src/status_im/data_store/realm/schemas/account/migrations.cljs +++ b/src/status_im/data_store/realm/schemas/account/migrations.cljs @@ -339,3 +339,10 @@ content-type (aget last-message "content-type")] (aset chat "last-message-content" content) (aset chat "last-message-content-type" content-type))))))) + +(defn v34 [old-realm new-realm] + (let [chats (.objects new-realm "chat")] + (dotimes [i (.-length chats)] + (let [chat (aget chats i) + chat-id (aget chat "chat-id")] + (aset chat "group-chat-local-version" 0))))) diff --git a/src/status_im/group_chats/core.cljs b/src/status_im/group_chats/core.cljs index 44373a3034..d5dc7d0ed6 100644 --- a/src/status_im/group_chats/core.cljs +++ b/src/status_im/group_chats/core.cljs @@ -52,12 +52,6 @@ js/JSON.parse (js->clj :keywordize-keys true))) -(defn joined? [public-key {:keys [members-joined]}] - (contains? members-joined public-key)) - -(defn invited? [my-public-key {:keys [contacts]}] - (contains? contacts my-public-key)) - (defn extract-creator "Takes a chat as an input, returns the creator" [{:keys [membership-updates]}] @@ -67,6 +61,20 @@ first :from)) +(defn joined-event? [public-key {:keys [members-joined] :as chat}] + (contains? members-joined public-key)) + +(defn joined? [public-key {:keys [group-chat-local-version] :as chat}] + ;; We consider group chats with local version of 0 as joined for local events + (or (zero? group-chat-local-version) + (joined-event? public-key chat))) + +(defn creator? [public-key chat] + (= public-key (extract-creator chat))) + +(defn invited? [my-public-key {:keys [contacts]}] + (contains? contacts my-public-key)) + (defn signature-material "Transform an update into a signable string" [chat-id events] @@ -130,13 +138,14 @@ ;; If a member has joined is listening to the shared topic and we send there ;; to ourselves we send always on contact-discovery to make sure all devices ;; are informed, in case of dropped messages. - ;; We send on the discovery topic to the creator as it's automatically - ;; joined or for contact that have not joined yet, - ;; for backward compatibility + ;; We check that it has explicitly joined, regardless of the local + ;; version of the group chat, for backward compatibility destinations (map (fn [member] - (if (and (joined? member chat) - (not= creator member) - (not= current-public-key member)) + (if (and + config/group-chats-publish-to-topic? + (joined-event? member chat) + (not= creator member) + (not= current-public-key member)) {:public-key member :chat chat-id} {:public-key member @@ -476,14 +485,11 @@ (fx/defn set-up-topic [cofx chat-id previous-chat] (let [my-public-key (accounts.db/current-public-key cofx) new-chat (get-in cofx [:db :chats chat-id])] - (cond - (and (not (joined? my-public-key previous-chat)) - (joined? my-public-key new-chat)) - (transport.public-chat/join-group-chat cofx chat-id) - - (and (joined? my-public-key previous-chat) - (not (joined? my-public-key new-chat))) - (transport.chat/unsubscribe-from-chat cofx chat-id)))) + ;; If we left the chat, teardown, otherwise upsert + (if (and (joined? my-public-key previous-chat) + (not (joined? my-public-key new-chat))) + (transport.chat/unsubscribe-from-chat cofx chat-id) + (transport.public-chat/join-group-chat cofx chat-id)))) (fx/defn handle-membership-update "Upsert chat and receive message if valid" @@ -495,8 +501,7 @@ raw-payload sender-signature] (let [dev-mode? (get-in cofx [:db :account/account :dev-mode?])] - (when (and config/group-chats-enabled? - (valid-chat-id? chat-id (extract-creator membership-update))) + (when (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))) @@ -505,15 +510,16 @@ new-group (build-group unwrapped-events) member? (contains? (:contacts new-group) my-public-key)] (fx/merge cofx - (models.chat/upsert-chat {:chat-id chat-id - :name (:name new-group) - :is-active (or member? - (get previous-chat :is-active true)) - :group-chat true - :membership-updates (into [] all-updates) - :admins (:admins new-group) - :members-joined (:members-joined new-group) - :contacts (:contacts new-group)}) + (models.chat/upsert-chat {:chat-id chat-id + :name (:name new-group) + :group-chat-local-version (get previous-chat :group-chat-local-version 1) + :is-active (or member? + (get previous-chat :is-active true)) + :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) (set-up-topic chat-id previous-chat) #(when (and message diff --git a/src/status_im/node/core.cljs b/src/status_im/node/core.cljs index 21e480a813..be74ba9cfc 100644 --- a/src/status_im/node/core.cljs +++ b/src/status_im/node/core.cljs @@ -113,12 +113,7 @@ :ShhextConfig {:BackupDisabledDataDir (utils.platform/no-backup-directory) :InstallationID installation-id :MailServerConfirmations config/mailserver-confirmations-enabled? - :PFSEnabled (or config/pfs-encryption-enabled? - ;; We don't check dev-mode? here as - ;; otherwise we would have to restart the node - ;; when the user enables it - config/group-chats-enabled? - (config/pairing-enabled? true))} + :PFSEnabled true} :RequireTopics (get-topics network)) (and diff --git a/src/status_im/ui/components/chat_icon/screen.cljs b/src/status_im/ui/components/chat_icon/screen.cljs index 317c71d18d..7d2df3dc87 100644 --- a/src/status_im/ui/components/chat_icon/screen.cljs +++ b/src/status_im/ui/components/chat_icon/screen.cljs @@ -160,15 +160,15 @@ :default-chat-icon (styles/default-chat-icon-profile colors/default-chat-color size) :default-chat-icon-text styles/default-chat-icon-text}]) -(defn profile-icon-view [photo-path name color edit? size] - (let [styles {:container {:width size :height size} - :online-view styles/online-view-profile - :online-dot-left styles/online-dot-left-profile - :online-dot-right styles/online-dot-right-profile - :size size - :chat-icon styles/chat-icon-profile - :default-chat-icon (styles/default-chat-icon-profile color size) - :default-chat-icon-text styles/default-chat-icon-text}] +(defn profile-icon-view [photo-path name color edit? size override-styles] + (let [styles (merge {:container {:width size :height size} + :online-view styles/online-view-profile + :online-dot-left styles/online-dot-left-profile + :online-dot-right styles/online-dot-right-profile + :size size + :chat-icon styles/chat-icon-profile + :default-chat-icon (styles/default-chat-icon-profile color size) + :default-chat-icon-text styles/default-chat-icon-text} override-styles)] [react/view (:container styles) (when edit? [react/view (styles/profile-icon-mask size)]) @@ -183,4 +183,4 @@ edit? :edit?}] (let [color colors/default-chat-color size 56] - [profile-icon-view photo-path name color edit? size])) + [profile-icon-view photo-path name color edit? size {}])) diff --git a/src/status_im/ui/screens/add_new/views.cljs b/src/status_im/ui/screens/add_new/views.cljs index 7d5727fe78..c7a9b0b406 100644 --- a/src/status_im/ui/screens/add_new/views.cljs +++ b/src/status_im/ui/screens/add_new/views.cljs @@ -22,16 +22,13 @@ :icon-opts {:color colors/blue} :on-press #(re-frame/dispatch [:navigate-to :new-chat])}] [action-button/action-separator] - ;; Hide behind flag (false by default), till everything is fixed in group chats - (when config/group-chats-enabled? - [action-button/action-button - {:label (i18n/label :t/start-group-chat) - :accessibility-label :start-group-chat-button - :icon :icons/contacts - :icon-opts {:color colors/blue} - :on-press #(re-frame/dispatch [:contact.ui/start-group-chat-pressed])}]) - (when config/group-chats-enabled? - [action-button/action-separator]) + [action-button/action-button + {:label (i18n/label :t/start-group-chat) + :accessibility-label :start-group-chat-button + :icon :icons/contacts + :icon-opts {:color colors/blue} + :on-press #(re-frame/dispatch [:contact.ui/start-group-chat-pressed])}] + [action-button/action-separator] [action-button/action-button {:label (i18n/label :t/new-public-group-chat) :accessibility-label :join-public-chat-button diff --git a/src/status_im/ui/screens/chat/styles/main.cljs b/src/status_im/ui/screens/chat/styles/main.cljs index 167a1d5480..d72e2fc273 100644 --- a/src/status_im/ui/screens/chat/styles/main.cljs +++ b/src/status_im/ui/screens/chat/styles/main.cljs @@ -233,7 +233,34 @@ :text-align :center}) (def empty-chat-text-name - {:color colors/black}) + {:margin-bottom 5 + :color colors/black}) (def join-button - {:margin-bottom 5}) + {:margin-top 24 + :margin-bottom 15}) + +(def group-chat-icon + {:color colors/white + :font-size 40 + :font-weight :bold + :line-height 55}) + +(def group-chat-join-footer + {:position :absolute + :justify-content :center + :margin-bottom 30 + :bottom 0}) + +(def group-chat-join-name + {:color :black + :font-weight :bold + :font-size 22}) + +(def group-chat-join-container + {:flex 1 + :align-items :center + :justify-content :center}) + +(def decline-chat + {:color colors/blue}) diff --git a/src/status_im/ui/screens/chat/views.cljs b/src/status_im/ui/screens/chat/views.cljs index 91b7bb3709..e278b1fd65 100644 --- a/src/status_im/ui/screens/chat/views.cljs +++ b/src/status_im/ui/screens/chat/views.cljs @@ -14,6 +14,7 @@ [status-im.ui.screens.chat.message.message :as message] [status-im.ui.screens.chat.message.options :as message-options] [status-im.ui.screens.chat.message.datemark :as message-datemark] + [status-im.ui.components.chat-icon.screen :as chat-icon.screen] [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] @@ -121,18 +122,35 @@ (i18n/label :t/empty-chat-description))]]))) (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])} + [buttons/secondary-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}] +(defn decline-chat [chat-id] + [react/touchable-highlight + {:on-press + #(re-frame/dispatch [:group-chats.ui/remove-chat-confirmed chat-id])} + [react/text {:style style/decline-chat} + (i18n/label :t/group-chat-decline-invitation)]]) + +(defview group-chat-join-section [my-public-key {:keys [name + group-chat + color + 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})]]])) + [react/view {:style {:margin-bottom 170}} + [chat-icon.screen/profile-icon-view nil name color false 100 {:default-chat-icon-text style/group-chat-icon}]] + [react/view {:style style/group-chat-join-footer} + [react/view {:style style/group-chat-join-container} + [react/view + [react/text {:style style/group-chat-join-name} name]] + [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})]] + [join-chat-button chat-id] + [decline-chat chat-id]]]])) (defview messages-view [{:keys [group-chat] :as chat} modal?] (letsubs [messages [:chats/current-chat-messages-stream] diff --git a/src/status_im/ui/screens/desktop/views.cljs b/src/status_im/ui/screens/desktop/views.cljs index 3469461e32..7cf829ac01 100644 --- a/src/status_im/ui/screens/desktop/views.cljs +++ b/src/status_im/ui/screens/desktop/views.cljs @@ -3,9 +3,9 @@ (:require [status-im.ui.screens.desktop.main.views :as main.views] [status-im.ui.components.react :as react] [status-im.ui.screens.intro.views :as intro.views] - [status-im.ui.screens.group.add-contacts.views :refer [contact-toggle-list - add-participants-toggle-list]] - [status-im.ui.screens.group.views :refer [new-group]] + [status-im.ui.screens.group.views :refer [contact-toggle-list + new-group + add-participants-toggle-list]] [status-im.ui.screens.profile.group-chat.views :refer [group-chat-profile]] [status-im.ui.screens.accounts.create.views :as create.views] [status-im.ui.screens.accounts.login.views :as login.views] diff --git a/src/status_im/ui/screens/group/add_contacts/views.cljs b/src/status_im/ui/screens/group/add_contacts/views.cljs deleted file mode 100644 index b402c213b4..0000000000 --- a/src/status_im/ui/screens/group/add_contacts/views.cljs +++ /dev/null @@ -1,131 +0,0 @@ -(ns status-im.ui.screens.group.add-contacts.views - (:require-macros [status-im.utils.views :refer [defview letsubs]]) - (:require [re-frame.core :as re-frame] - [status-im.i18n :as i18n] - [status-im.utils.platform :as utils.platform] - [status-im.ui.components.button.view :as buttons] - [status-im.constants :as constants] - [status-im.ui.components.contact.contact :refer [toggle-contact-view]] - [status-im.ui.components.list.views :as list] - [status-im.ui.components.list-selection :as list-selection] - [status-im.ui.components.react :as react] - [status-im.ui.components.status-bar.view :refer [status-bar]] - [status-im.ui.components.toolbar.view :as toolbar] - [status-im.ui.screens.group.styles :as styles])) - -(defn- on-toggle [allow-new-users? checked? public-key] - (cond - - checked? - (re-frame/dispatch [:deselect-contact public-key allow-new-users?]) - - ;; Only allow new users if not reached the maximum - (and (not checked?) - allow-new-users?) - (re-frame/dispatch [:select-contact public-key allow-new-users?]))) - -(defn- on-toggle-participant [allow-new-users? checked? public-key] - (cond - - checked? - (re-frame/dispatch [:deselect-participant public-key allow-new-users?]) - - ;; Only allow new users if not reached the maximum - (and (not checked?) - allow-new-users?) - (re-frame/dispatch [:select-participant public-key allow-new-users?]))) - -(defn- group-toggle-contact [allow-new-users? contact] - [toggle-contact-view - contact - :is-contact-selected? - (partial on-toggle allow-new-users?) - (and (not (:is-contact-selected? contact)) - (not allow-new-users?))]) - -(defn- group-toggle-participant [allow-new-users? contact] - [toggle-contact-view - contact - :is-participant-selected? - (partial on-toggle-participant allow-new-users?) - ;; Disable if not-checked and we don't allow new users - (and (not (:is-participant-selected? contact)) - (not allow-new-users?))]) - -(defn- handle-invite-friends-pressed [] - (if utils.platform/desktop? - (re-frame/dispatch [:navigate-to :new-contact]) - (list-selection/open-share {:message (i18n/label :t/get-status-at)}))) - -(defn- toggle-list-toolbar [{:keys [handler count label]} title] - [toolbar/toolbar {} - toolbar/default-nav-back - [toolbar/content-title title] - (when (pos? count) - [toolbar/text-action {:handler handler - :accessibility-label :next-button} - label])]) - -(defn toggle-list [contacts render-function] - [react/scroll-view {:flex 1} - (if utils.platform/desktop? - (for [contact contacts] - ^{:key (:public-key contact)} - (render-function contact)) - [list/flat-list {:style styles/contacts-list - :data contacts - :key-fn :address - :render-fn render-function - :keyboardShouldPersistTaps :always}])]) - -(defn no-contacts [] - [react/view {:style {:flex 1 - :justify-content :center - :align-items :center}} - [react/text - {:style styles/no-contact-text} - (i18n/label :t/group-chat-no-contacts)] - [buttons/secondary-button {:on-press handle-invite-friends-pressed} (i18n/label :t/invite-friends)]]) - -(defn number-of-participants-disclaimer [number-of-participants-available] - [react/view {:style styles/number-of-participants-disclaimer} - [react/text (if (> number-of-participants-available - 0) - (i18n/label-pluralize number-of-participants-available :t/available-participants) - (i18n/label :t/no-more-participants-available))]]) - -;; Start group chat -(defview contact-toggle-list [] - (letsubs [contacts [:contacts/all-added-people-contacts] - selected-contacts-count [:selected-contacts-count]] - [react/keyboard-avoiding-view {:style styles/group-container} - [status-bar] - [toggle-list-toolbar {:handler #(re-frame/dispatch [:navigate-to :new-group]) - :label (i18n/label :t/next) - :count (pos? selected-contacts-count)} - (i18n/label :t/group-chat)] - (when (seq contacts) - [number-of-participants-disclaimer (- (dec constants/max-group-chat-participants) selected-contacts-count)]) - (if (seq contacts) - [toggle-list contacts (partial group-toggle-contact (< selected-contacts-count (dec constants/max-group-chat-participants)))] - [no-contacts])])) - -;; Add participants to existing group chat -(defview add-participants-toggle-list [] - (letsubs [contacts [:contacts/all-contacts-not-in-current-chat] - {:keys [name] :as current-chat} [:chats/current-chat] - selected-contacts-count [:selected-participants-count]] - (let [current-participants-count (count (:contacts current-chat))] - [react/keyboard-avoiding-view {:style styles/group-container} - [status-bar] - [toggle-list-toolbar {:count selected-contacts-count - :handler #(do - (re-frame/dispatch [:group-chats.ui/add-members-pressed]) - (re-frame/dispatch [:navigate-back])) - :label (i18n/label :t/add)} - name] - - [number-of-participants-disclaimer (- constants/max-group-chat-participants current-participants-count)] - (when (seq contacts) - [toggle-list contacts (partial group-toggle-participant (< (+ current-participants-count - selected-contacts-count) constants/max-group-chat-participants))])]))) diff --git a/src/status_im/ui/screens/group/styles.cljs b/src/status_im/ui/screens/group/styles.cljs index 67d54e1031..04f8b1b9ee 100644 --- a/src/status_im/ui/screens/group/styles.cljs +++ b/src/status_im/ui/screens/group/styles.cljs @@ -30,3 +30,22 @@ :font-size 12 :margin-horizontal 17}) +(def bottom-container + {:padding-horizontal 12 + :padding-vertical 15}) + +(def toolbar-header-container + {:align-items :center}) + +(def toolbar-header + {:font-size 15 + :color colors/black}) + +(def toolbar-sub-header + {:font-size 15 + :color colors/gray}) + +(def no-contacts + {:flex 1 + :justify-content :center + :align-items :center}) diff --git a/src/status_im/ui/screens/group/views.cljs b/src/status_im/ui/screens/group/views.cljs index 394d8bb6f1..c35b3bce2b 100644 --- a/src/status_im/ui/screens/group/views.cljs +++ b/src/status_im/ui/screens/group/views.cljs @@ -1,8 +1,17 @@ (ns status-im.ui.screens.group.views (:require-macros [status-im.utils.views :as views]) (:require [cljs.spec.alpha :as spec] + [clojure.string :as string] [re-frame.core :as re-frame] [status-im.i18n :as i18n] + [status-im.ui.screens.main-tabs.styles :as main-tabs.styles] + [status-im.ui.components.styles :as components.styles] + [status-im.constants :as constants] + [status-im.utils.platform :as utils.platform] + [status-im.ui.components.contact.contact :refer [toggle-contact-view]] + [status-im.ui.components.button.view :as buttons] + [status-im.ui.components.list-selection :as list-selection] + [status-im.ui.components.common.common :as components.common] [status-im.ui.components.react :as react] [status-im.ui.components.list.views :as list] [status-im.ui.components.status-bar.view :as status-bar] @@ -12,38 +21,123 @@ [status-im.ui.screens.add-new.styles :as add-new.styles] [status-im.ui.screens.group.styles :as styles])) -(views/defview group-name-view [] - (views/letsubs [new-group-name [:get :new-chat-name]] - [react/view add-new.styles/input-container - [react/text-input - {:auto-focus true - :on-change-text #(re-frame/dispatch [:set :new-chat-name %]) - :default-value new-group-name - :placeholder (i18n/label :t/set-a-topic) - :style add-new.styles/input - :accessibility-label :chat-name-input}]])) +(views/defview group-name-view [new-group-name] + [react/view add-new.styles/input-container + [react/text-input + {:auto-focus true + :on-change-text #(re-frame/dispatch [:set :new-chat-name %]) + :default-value new-group-name + :placeholder (i18n/label :t/set-a-topic) + :style add-new.styles/input + :accessibility-label :chat-name-input}]]) (defn- render-contact [contact] [contact/contact-view {:contact contact :style styles/contact :accessibility-label :chat-member-item}]) -(defn- toolbar [group-name save-btn-enabled?] +(defn- toolbar [header sub-header] [toolbar/toolbar {} toolbar/default-nav-back - [toolbar/content-title (i18n/label :t/group-chat)] - (when save-btn-enabled? - (let [handler #(re-frame/dispatch [:group-chats.ui/create-pressed group-name])] - (if platform/android? - [toolbar/actions [{:icon :icons/ok - :icon-opts {:color :blue - :accessibility-label :create-button} - :handler handler}]] - [toolbar/text-action {:handler handler - :accessibility-label :create-button} - (i18n/label :t/create)])))]) + [react/view {:style styles/toolbar-header-container} + [react/view + [react/text {:style styles/toolbar-header} header]] + [react/view + [react/text {:style styles/toolbar-sub-header} sub-header]]]]) -;; New Group Chat +(defn- on-toggle [allow-new-users? checked? public-key] + (cond + + checked? + (re-frame/dispatch [:deselect-contact public-key allow-new-users?]) + + ;; Only allow new users if not reached the maximum + (and (not checked?) + allow-new-users?) + (re-frame/dispatch [:select-contact public-key allow-new-users?]))) + +(defn- on-toggle-participant [allow-new-users? checked? public-key] + (cond + + checked? + (re-frame/dispatch [:deselect-participant public-key allow-new-users?]) + + ;; Only allow new users if not reached the maximum + (and (not checked?) + allow-new-users?) + (re-frame/dispatch [:select-participant public-key allow-new-users?]))) + +(defn- group-toggle-contact [allow-new-users? contact] + [toggle-contact-view + contact + :is-contact-selected? + (partial on-toggle allow-new-users?) + (and (not (:is-contact-selected? contact)) + (not allow-new-users?))]) + +(defn- group-toggle-participant [allow-new-users? contact] + [toggle-contact-view + contact + :is-participant-selected? + (partial on-toggle-participant allow-new-users?) + ;; Disable if not-checked and we don't allow new users + (and (not (:is-participant-selected? contact)) + (not allow-new-users?))]) + +(defn- handle-invite-friends-pressed [] + (if utils.platform/desktop? + (re-frame/dispatch [:navigate-to :new-contact]) + (list-selection/open-share {:message (i18n/label :t/get-status-at)}))) + +(defn toggle-list [contacts render-function] + [react/scroll-view {:flex 1} + (if utils.platform/desktop? + (for [contact contacts] + ^{:key (:public-key contact)} + (render-function contact)) + [list/flat-list {:style styles/contacts-list + :data contacts + :key-fn :address + :render-fn render-function + :keyboardShouldPersistTaps :always}])]) + +(defn no-contacts [] + [react/view {:style styles/no-contacts} + [react/text + {:style styles/no-contact-text} + (i18n/label :t/group-chat-no-contacts)] + [buttons/secondary-button {:on-press handle-invite-friends-pressed} (i18n/label :t/invite-friends)]]) + +(views/defview bottom-container [{:keys [on-press disabled label]}] + [react/view {:style main-tabs.styles/tabs-container} + [react/view {:style components.styles/flex}] + [react/view {:style styles/bottom-container} + [components.common/bottom-button + {:forward? true + :accessibility-label :next-button + :label label + :disabled? disabled + :on-press on-press}]]]) + +;; Start group chat +(views/defview contact-toggle-list [] + (views/letsubs [contacts [:contacts/all-added-people-contacts] + selected-contacts-count [:selected-contacts-count]] + [react/keyboard-avoiding-view {:style styles/group-container} + [status-bar/status-bar] + [toolbar + (i18n/label :t/new-group-chat) + (i18n/label :t/group-chat-members-count + {:selected selected-contacts-count + :max (dec constants/max-group-chat-participants)})] + (if (seq contacts) + [toggle-list contacts (partial group-toggle-contact (< selected-contacts-count (dec constants/max-group-chat-participants)))] + [no-contacts]) + [bottom-container {:on-press #(re-frame/dispatch [:navigate-to :new-group]) + :disabled (zero? selected-contacts-count) + :label (i18n/label :t/next)}]])) + +;; Set name of new group-chat (views/defview new-group [] (views/letsubs [contacts [:selected-group-contacts] group-name [:get :new-chat-name]] @@ -51,13 +145,42 @@ [react/keyboard-avoiding-view (merge {:behavior :padding} styles/group-container) [status-bar/status-bar] - [toolbar group-name save-btn-enabled?] + [toolbar + (i18n/label :t/new-group-chat) + (i18n/label :t/group-chat-members-count + {:selected (count contacts) + :max (dec constants/max-group-chat-participants)})] [group-name-view] - [list/list-with-label {:flex 1} - (i18n/label :t/members-title) - [list/flat-list {:data contacts - :key-fn :address - :render-fn render-contact - :bounces false - :keyboard-should-persist-taps :always - :enable-empty-sections true}]]]))) + [react/scroll-view + [list/list-with-label {:flex 1} + (i18n/label :t/members-title) + [list/flat-list {:data contacts + :key-fn :address + :render-fn render-contact + :bounces false + :keyboard-should-persist-taps :always + :enable-empty-sections true}]]] + [bottom-container {:on-press #(re-frame/dispatch [:group-chats.ui/create-pressed group-name]) + :disabled (string/blank? group-name) + :label (i18n/label :t/create-group-chat)}]]))) + +;; Add participants to existing group chat +(views/defview add-participants-toggle-list [] + (views/letsubs [contacts [:contacts/all-contacts-not-in-current-chat] + {:keys [name] :as current-chat} [:chats/current-chat] + selected-contacts-count [:selected-participants-count]] + (let [current-participants-count (count (:contacts current-chat))] + [react/keyboard-avoiding-view {:style styles/group-container} + [status-bar/status-bar] + [toolbar + name + (i18n/label :t/group-chat-members-count + {:selected (dec (+ current-participants-count selected-contacts-count)) + :max (dec constants/max-group-chat-participants)})] + (when (seq contacts) + [toggle-list contacts (partial group-toggle-participant (< (+ current-participants-count + selected-contacts-count) constants/max-group-chat-participants))]) + [bottom-container {:on-press + #(re-frame/dispatch [:group-chats.ui/add-members-pressed]) + :disabled (zero? selected-contacts-count) + :label (i18n/label :t/add)}]]))) diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index b9ddf10e98..40b5b2c2f6 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -20,9 +20,9 @@ [status-im.ui.screens.qr-scanner.views :refer [qr-scanner]] - [status-im.ui.screens.group.views :refer [new-group]] - [status-im.ui.screens.group.add-contacts.views :refer [contact-toggle-list - add-participants-toggle-list]] + [status-im.ui.screens.group.views :refer [new-group + contact-toggle-list + add-participants-toggle-list]] [status-im.ui.screens.profile.user.views :as profile.user] [status-im.ui.screens.profile.contact.views :as profile.contact] [status-im.ui.screens.profile.group-chat.views :as profile.group-chat] diff --git a/src/status_im/utils/config.cljs b/src/status_im/utils/config.cljs index 1c982cb2cf..c276e7e407 100644 --- a/src/status_im/utils/config.cljs +++ b/src/status_im/utils/config.cljs @@ -17,7 +17,7 @@ (def bootnodes-settings-enabled? (enabled? (get-config :BOOTNODES_SETTINGS_ENABLED "1"))) (def rpc-networks-only? (enabled? (get-config :RPC_NETWORKS_ONLY "1"))) -(def group-chats-enabled? (enabled? (get-config :GROUP_CHATS_ENABLED "0"))) +(def group-chats-publish-to-topic? (enabled? (get-config :GROUP_CHATS_PUBLISH_TO_TOPIC "0"))) (defn pairing-enabled? [dev-mode?] (and (enabled? (get-config :PAIRING_ENABLED "0")) (or dev-mode? platform/desktop?))) diff --git a/test/cljs/status_im/test/node/core.cljs b/test/cljs/status_im/test/node/core.cljs index 210f636581..8bf4e2c0fc 100644 --- a/test/cljs/status_im/test/node/core.cljs +++ b/test/cljs/status_im/test/node/core.cljs @@ -15,17 +15,4 @@ cofx {:db {:accounts/accounts {address {:installation-id "id"}}}}] (testing "installation-id" (let [actual (parse-node-config (node/start cofx address))] - (is (= "id" (:InstallationID actual))))) - (testing "pfs & group chats disabled" - (with-redefs [config/pfs-encryption-enabled? false - config/group-chats-enabled? false] - (let [actual (parse-node-config (node/start cofx address))] - (is (not (:PFSEnabled actual))))) - (testing "pfs is enabled" - (with-redefs [config/pfs-encryption-enabled? true] - (let [actual (parse-node-config (node/start cofx address))] - (is (:PFSEnabled actual))))) - (testing "group chats is enabled" - (with-redefs [config/group-chats-enabled? true] - (let [actual (parse-node-config (node/start cofx address))] - (is (:PFSEnabled actual)))))))) + (is (= "id" (:InstallationID actual))))))) diff --git a/translations/en.json b/translations/en.json index 2aa927e17e..1d9c359c7b 100644 --- a/translations/en.json +++ b/translations/en.json @@ -47,7 +47,10 @@ }, "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", + "join-group-chat": "Join group", + "create-group-chat": "Create group chat", + "group-chat-decline-invitation": "Decline invitation", + "group-chat-members-count": "{{selected}}/{{max}} members", "group-chat-created": "*{{member}}* created the group *{{name}}*", "group-chat-admin": "Admin", "group-chat-name-changed": "*{{member}}* changed the group's name to *{{name}}*",