From 5f910a0beced28ae5bfb4086e93fa56659c219db Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Tue, 4 Dec 2018 13:03:41 +0100 Subject: [PATCH] Sync public chats Signed-off-by: Andrea Maria Piana --- src/status_im/chat/models.cljs | 5 +- src/status_im/events.cljs | 5 +- src/status_im/pairing/core.cljs | 52 ++++++++--- src/status_im/transport/impl/send.cljs | 2 + src/status_im/transport/message/pairing.cljs | 2 +- src/status_im/transport/message/transit.cljs | 8 +- .../desktop/main/tabs/profile/views.cljs | 8 +- src/status_im/ui/screens/pairing/subs.cljs | 4 + src/status_im/ui/screens/pairing/views.cljs | 93 +++++++++++++------ test/cljs/status_im/test/pairing/core.cljs | 21 ++++- translations/en.json | 4 + 11 files changed, 150 insertions(+), 54 deletions(-) diff --git a/src/status_im/chat/models.cljs b/src/status_im/chat/models.cljs index f867fae29d..0b8cfab904 100644 --- a/src/status_im/chat/models.cljs +++ b/src/status_im/chat/models.cljs @@ -240,10 +240,11 @@ (fx/defn start-public-chat "Starts a new public chat" - [cofx topic opts] + [cofx topic {:keys [dont-navigate?] :as opts}] (fx/merge cofx (add-public-chat topic) - (navigate-to-chat topic opts) + #(when-not dont-navigate? + (navigate-to-chat % topic opts)) (public-chat/join-public-chat topic) (when platform/desktop? (desktop.events/change-tab :home)))) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 9e5c12de97..5385b0c9f8 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -671,7 +671,10 @@ (handlers/register-handler-fx :chat.ui/start-public-chat (fn [cofx [_ topic opts]] - (chat/start-public-chat cofx topic opts))) + (fx/merge + cofx + (chat/start-public-chat topic opts) + (pairing/sync-public-chat topic)))) (handlers/register-handler-fx :chat.ui/remove-chat diff --git a/src/status_im/pairing/core.cljs b/src/status_im/pairing/core.cljs index eddac56868..7ddf7c83ef 100644 --- a/src/status_im/pairing/core.cljs +++ b/src/status_im/pairing/core.cljs @@ -6,15 +6,18 @@ [status-im.ui.screens.navigation :as navigation] [status-im.utils.config :as config] [status-im.utils.platform :as utils.platform] + [status-im.chat.models :as models.chat] [status-im.accounts.db :as accounts.db] [status-im.transport.message.protocol :as protocol] [status-im.data-store.installations :as data-store.installations] [status-im.native-module.core :as native-module] [status-im.utils.identicon :as identicon] [status-im.data-store.contacts :as data-store.contacts] + [status-im.data-store.accounts :as data-store.accounts] [status-im.transport.message.pairing :as transport.pairing])) (def contact-batch-n 4) +(def max-installations 2) (defn- parse-response [response-js] (-> response-js @@ -94,29 +97,41 @@ (not (get-in db [:pairing/installations installation-id]))) (fx/merge cofx (upsert-installation new-installation) - #(when-not (get-in % [:db :pairing/prompt-user-pop-up]) + #(when-not (or (get-in % [:db :pairing/prompt-user-pop-up]) + (= :installations (:view-id db))) (prompt-user-on-new-installation %)))))))) (defn sync-installation-account-message [{:keys [db]}] (let [account (-> db :account/account (select-keys account-mergeable-keys))] - (transport.pairing/SyncInstallation. {} account))) + (transport.pairing/SyncInstallation. {} account {}))) (defn- contact-batch->sync-installation-message [batch] (let [contacts-to-sync (reduce (fn [acc {:keys [public-key] :as contact}] (assoc acc public-key (dissoc contact :photo-path))) {} batch)] - (transport.pairing/SyncInstallation. contacts-to-sync nil))) + (transport.pairing/SyncInstallation. contacts-to-sync {} {}))) + +(defn- chats->sync-installation-messages [{:keys [db]}] + (->> db + :chats + vals + (filter :public?) + (filter :is-active) + (map #(select-keys % [:chat-id :public?])) + (map #(transport.pairing/SyncInstallation. {} {} %)))) (defn sync-installation-messages [{:keys [db] :as cofx}] (let [contacts (:contacts/contacts db) contact-batches (partition-all contact-batch-n (->> contacts vals (remove :dapp?)))] - (conj (mapv contact-batch->sync-installation-message contact-batches) - (sync-installation-account-message cofx)))) + (concat (mapv contact-batch->sync-installation-message contact-batches) + + [(sync-installation-account-message cofx)] + (chats->sync-installation-messages cofx)))) (defn enable [{:keys [db]} installation-id] {:db (assoc-in db @@ -154,8 +169,12 @@ (native-module/disable-installation installation-id (partial handle-disable-installation-response installation-id))) -(defn enable-fx [_ installation-id] - {:pairing/enable-installation installation-id}) +(defn enable-fx [cofx installation-id] + (if (< (count (filter :enabled? (get-in cofx [:db :pairing/installations]))) max-installations) + {:pairing/enable-installation installation-id} + {:utils/show-popup {:title (i18n/label :t/pairing-maximum-number-reached-title) + + :content (i18n/label :t/pairing-maximum-number-reached-content)}})) (defn disable-fx [_ installation-id] {:pairing/disable-installation installation-id}) @@ -184,6 +203,11 @@ (has-paired-installations? cofx)) (protocol/send payload nil cofx)))) +(fx/defn sync-public-chat [cofx chat-id] + (let [sync-message (transport.pairing/SyncInstallation. {} {} {:public? true + :chat-id chat-id})] + (send-installation-message-fx cofx sync-message))) + (defn send-installation-messages [cofx] ;; The message needs to be broken up in chunks as we hit the whisper size limit (let [sync-messages (sync-installation-messages cofx) @@ -204,16 +228,20 @@ {} contacts)) -(defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts account]} sender] +(defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts account chat]} sender] (let [dev-mode? (get-in db [:account/account :dev-mode?])] (when (and (config/pairing-enabled? dev-mode?) (= sender (accounts.db/current-public-key cofx))) (let [new-contacts (merge-contacts (:contacts/contacts db) (ensure-photo-path contacts)) new-account (merge-account (:account/account db) account)] - {:db (assoc db - :contacts/contacts new-contacts - :account/account new-account) - :data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]})))) + (fx/merge cofx + {:db (assoc db + :contacts/contacts new-contacts + :account/account new-account) + :data-store/base-tx [(data-store.accounts/save-account-tx new-account)] + :data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]} + #(when (:public? chat) + (models.chat/start-public-chat % (:chat-id chat) {:dont-navigate? true}))))))) (defn handle-pair-installation [{:keys [db] :as cofx} {:keys [installation-id device-type]} timestamp sender] (let [dev-mode? (get-in db [:account/account :dev-mode?])] diff --git a/src/status_im/transport/impl/send.cljs b/src/status_im/transport/impl/send.cljs index fa24c86138..48e73168bb 100644 --- a/src/status_im/transport/impl/send.cljs +++ b/src/status_im/transport/impl/send.cljs @@ -31,6 +31,7 @@ (select-keys (get-in cofx [:db :contacts/contacts]) [chat-id]) + nil nil)] (fx/merge cofx (protocol/init-chat {:chat-id chat-id @@ -47,6 +48,7 @@ (select-keys (get-in cofx [:db :contacts/contacts]) [chat-id]) + nil nil) success-event [:transport/contact-message-sent chat-id] chat (get-in db [:transport/chats chat-id]) diff --git a/src/status_im/transport/message/pairing.cljs b/src/status_im/transport/message/pairing.cljs index 1e7ef4cd55..19aa96c984 100644 --- a/src/status_im/transport/message/pairing.cljs +++ b/src/status_im/transport/message/pairing.cljs @@ -12,7 +12,7 @@ (log/warn "failed sync installation validation" (spec/explain :message/pair-installation this))))) (defrecord SyncInstallation - [contacts account] + [contacts account chat] protocol/StatusMessage (validate [this] (if (spec/valid? :message/sync-installation this) diff --git a/src/status_im/transport/message/transit.cljs b/src/status_im/transport/message/transit.cljs index 3a46385092..dbccd7ac28 100644 --- a/src/status_im/transport/message/transit.cljs +++ b/src/status_im/transport/message/transit.cljs @@ -86,8 +86,8 @@ (deftype SyncInstallationHandler [] Object (tag [this v] "p1") - (rep [this {:keys [contacts account]}] - #js [contacts account])) + (rep [this {:keys [contacts account chat]}] + #js [contacts account chat])) (deftype PairInstallationHandler [] Object @@ -154,8 +154,8 @@ (contact/ContactUpdate. name profile-image address fcm-token)) "g5" (fn [[chat-id membership-updates message]] (group-chat/GroupMembershipUpdate. chat-id membership-updates message)) - "p1" (fn [[contacts account]] - (pairing/SyncInstallation. contacts account)) + "p1" (fn [[contacts account chat]] + (pairing/SyncInstallation. contacts account chat)) "p2" (fn [[installation-id device-type]] (pairing/PairInstallation. installation-id device-type))}})) diff --git a/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs b/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs index c5aa9b34c0..1b1e82f7a3 100644 --- a/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs +++ b/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs @@ -77,11 +77,12 @@ [react/text {:style styles/qr-code-copy-text} (i18n/label :copy-qr)]]]]])) -(defn installations-section [installations] +(defn installations-section [your-installation-id installations] [react/view [pairing.views/pair-this-device] [pairing.views/sync-devices] [react/view {:style pairing.styles/installation-list} + [pairing.views/your-device your-installation-id] (for [installation installations] ^{:key (:installation-id installation)} [react/view {:style {:margin-bottom 10}} @@ -176,10 +177,11 @@ [logging-display]]))) (views/defview installations [] - (views/letsubs [installations [:pairing/installations]] + (views/letsubs [installations [:pairing/installations] + installation-id [:pairing/installation-id]] [react/scroll-view (when (config/pairing-enabled? true) - (installations-section installations))])) + (installations-section installation-id installations))])) (views/defview backup-recovery-phrase [] [profile.recovery/backup-seed]) diff --git a/src/status_im/ui/screens/pairing/subs.cljs b/src/status_im/ui/screens/pairing/subs.cljs index 6cd3ebcc1c..187d46cb38 100644 --- a/src/status_im/ui/screens/pairing/subs.cljs +++ b/src/status_im/ui/screens/pairing/subs.cljs @@ -8,3 +8,7 @@ (->> installations vals (sort-by (comp unchecked-negate :last-paired))))) + +(re-frame/reg-sub :pairing/installation-id + :<- [:get :account/account] + :installation-id) diff --git a/src/status_im/ui/screens/pairing/views.cljs b/src/status_im/ui/screens/pairing/views.cljs index 304464baf1..abcaa2cf20 100644 --- a/src/status_im/ui/screens/pairing/views.cljs +++ b/src/status_im/ui/screens/pairing/views.cljs @@ -2,13 +2,16 @@ (:require-macros [status-im.utils.views :as views]) (:require [re-frame.core :as re-frame] [status-im.i18n :as i18n] + [reagent.core :as reagent] [status-im.utils.config :as config] [status-im.ui.screens.main-tabs.styles :as main-tabs.styles] + [status-im.ui.components.colors :as colors] [status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.screens.home.styles :as home.styles] [status-im.utils.platform :as utils.platform] [status-im.utils.gfycat.core :as gfycat] [status-im.ui.components.button.view :as buttons] + [status-im.ui.components.checkbox.view :as checkbox.views] [status-im.ui.components.list.views :as list] [status-im.ui.components.react :as react] [status-im.ui.components.status-bar.view :as status-bar] @@ -17,6 +20,8 @@ [status-im.ui.screens.profile.components.views :as profile.components] [status-im.ui.screens.pairing.styles :as styles])) +(def syncing (reagent/atom false)) + (defn icon-style [{:keys [width height] :as style}] (if utils.platform/desktop? {:container-style {:width width @@ -26,25 +31,37 @@ style)) (defn synchronize-installations! [] + (reset! syncing true) + ;; Currently we don't know how long it takes, so we just disable for 10s, to avoid + ;; spamming + (js/setTimeout #(reset! syncing false) 10000) (re-frame/dispatch [:pairing.ui/synchronize-installation-pressed])) (defn pair! [] (re-frame/dispatch [:pairing.ui/pair-devices-pressed])) -(defn enable-installation! [installation-id _] +(defn enable-installation! [installation-id] (re-frame/dispatch [:pairing.ui/enable-installation-pressed installation-id])) -(defn disable-installation! [installation-id _] +(defn disable-installation! [installation-id] (re-frame/dispatch [:pairing.ui/disable-installation-pressed installation-id])) -(defn footer [] - [react/touchable-highlight {:on-press synchronize-installations! +(defn toggle-enabled! [installation-id enabled? _] + (if enabled? + (disable-installation! installation-id) + (enable-installation! installation-id))) + +(defn footer [syncing] + [react/touchable-highlight {:on-press (when-not @syncing + synchronize-installations!) :style main-tabs.styles/tabs-container} [react/view {:style styles/footer-content} [react/text {:style styles/footer-text} - (i18n/label :t/sync-all-devices)]]]) + (if @syncing + (i18n/label :t/syncing-devices) + (i18n/label :t/sync-all-devices))]]]) (defn pair-this-device [] [react/touchable-highlight {:on-press pair! @@ -68,14 +85,30 @@ [icons/icon :icons/wnode (icon-style (styles/pairing-button-icon true))]]] [react/view {:style styles/pairing-actions-text} [react/view - [react/text {:style styles/pair-this-device-title} (i18n/label :t/sync-all-devices)]]]]]) + [react/text {:style styles/pair-this-device-title} + (if @syncing + (i18n/label :t/syncing-devices) + (i18n/label :t/sync-all-devices))]]]]]) + +(defn your-device [installation-id] + [react/view {:style styles/installation-item} + [react/view {:style (styles/pairing-button true)} + [icons/icon (if utils.platform/desktop? + :icons/desktop + :icons/mobile) + (icon-style (styles/pairing-button-icon true))]] + [react/view {:style styles/pairing-actions-text} + [react/view + [react/text {:style styles/installation-item-name-text} + (str + (gfycat/generate-gfy installation-id) + " (" + (i18n/label :t/you) + ")")]]]]) (defn render-row [{:keys [device-type enabled? installation-id]}] [react/touchable-highlight - {:on-press (if enabled? - (partial disable-installation! installation-id) - (partial enable-installation! installation-id)) - :accessibility-label :installation-item} + {:accessibility-label :installation-item} [react/view {:style styles/installation-item} [react/view {:style (styles/pairing-button enabled?)} [icons/icon (if (= "desktop" @@ -86,28 +119,35 @@ [react/view {:style styles/pairing-actions-text} [react/view [react/text {:style styles/installation-item-name-text} - (gfycat/generate-gfy installation-id)]] - [react/view - [react/text {:style styles/installation-status} - (if enabled? - (i18n/label :t/syncing-enabled) - (i18n/label :t/syncing-disabled))]]]]]) + (gfycat/generate-gfy installation-id)]]] + [react/view + (if utils.platform/ios? + ;; On IOS switches seems to be broken, they take up value of dev-mode? (so if dev mode is on they all show to be on). + ;; Replacing therefore with checkbox until I have more time to investigate + (checkbox.views/plain-checkbox {:checked? enabled? + :on-value-change (partial toggle-enabled! installation-id enabled?)}) + [react/switch {:on-tint-color colors/blue + :value enabled? + :on-value-change (partial toggle-enabled! installation-id enabled?)}])]]]) -(defn render-rows [installations] +(defn render-rows [installation-id installations] [react/scroll-view {:style styles/wrapper} - [list/flat-list {:data installations - :default-separator? false - :key-fn :installation-id - :render-fn render-row}]]) + [your-device installation-id] + (when (seq installations) + [list/flat-list {:data installations + :default-separator? false + :key-fn :installation-id + :render-fn render-row}])]) -(defn installations-list [installations] +(defn installations-list [installation-id installations] [react/view {:style styles/installation-list} [react/view {:style styles/paired-devices-title} [react/text (i18n/label :t/paired-devices)]] - (render-rows installations)]) + (render-rows installation-id installations)]) (views/defview installations [] - (views/letsubs [installations [:pairing/installations]] + (views/letsubs [installation-id [:pairing/installation-id] + installations [:pairing/installations]] [react/view {:flex 1} [status-bar/status-bar] [toolbar/toolbar {} @@ -115,6 +155,5 @@ [toolbar/content-title (i18n/label :t/devices)]] [react/scroll-view {:style {:background-color :white}} [pair-this-device] - (when (seq installations) - [installations-list installations])] - (when (seq installations) [footer])])) + [installations-list installation-id installations]] + (when (seq installations) [footer syncing])])) diff --git a/test/cljs/status_im/test/pairing/core.cljs b/test/cljs/status_im/test/pairing/core.cljs index 1c7ae65139..c5da6e826c 100644 --- a/test/cljs/status_im/test/pairing/core.cljs +++ b/test/cljs/status_im/test/pairing/core.cljs @@ -153,7 +153,15 @@ (let [cofx {:db {:account/account new-account}} sync-message {:account old-account}] (is (= new-account (get-in (pairing/handle-sync-installation cofx sync-message "us") - [:db :account/account]))))))))) + [:db :account/account]))))))) + (testing "syncing public chats" + (let [cofx {:db {:account/account {:public-key "us"}}}] + (testing "a new chat" + (let [sync-message {:chat {:public? true + :chat-id "status" + :is-active true}}] + (is (get-in (pairing/handle-sync-installation cofx sync-message "us") + [:db :chats "status"])))))))) (deftest handle-pair-installation-test (with-redefs [config/pairing-enabled? (constantly true)] @@ -183,6 +191,9 @@ :name "name" :photo-path "photo-path" :last-updated 1} + :chats {"status" {:public? true + :is-active true + :chat-id "status"}} :contacts/contacts {"contact-1" {:name "contact-1" :public-key "contact-1"} "contact-2" {:name "contact-2" @@ -201,12 +212,14 @@ :public-key "contact-3"} "contact-4" {:name "contact-4" :public-key "contact-4"}} - nil) + {} {}) (transport.pairing/SyncInstallation. {"contact-5" {:name "contact-5" - :public-key "contact-5"}} nil) + :public-key "contact-5"}} {} {}) (transport.pairing/SyncInstallation. {} {:photo-path "photo-path" :name "name" - :last-updated 1})]] + :last-updated 1} {}) + (transport.pairing/SyncInstallation. {} {} {:public? true + :chat-id "status"})]] (is (= expected (pairing/sync-installation-messages cofx)))))) (deftest handle-bundles-added-test diff --git a/translations/en.json b/translations/en.json index 8f88ce14c8..ef17b95eef 100644 --- a/translations/en.json +++ b/translations/en.json @@ -9,10 +9,14 @@ "pair": "Pair devices", "pair-this-device": "Pair this device", "pair-this-device-description": "Pair your devices to sync contacts and chats between them", + "you": "you", "syncing-enabled": "Syncing enabled", "syncing-disabled": "Syncing disabled", "sync-all-devices": "Sync all devices", + "syncing-devices": "Syncing...", "paired-devices": "Paired devices", + "pairing-maximum-number-reached-title": "Max number of devices reached", + "pairing-maximum-number-reached-content": "Please disable one of your devices before enabling a new one.", "pairing-new-installation-detected-title": "New device detected", "pairing-new-installation-detected-content": "A new device has been detected.\nIn order to use your devices correctly, it's important to pair and enable them before using them.\nPlease go to the device section under settings to pair your devices.", "pairing-go-to-installation": "Go to pairing settings",