Sync public chats

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2018-12-04 13:03:41 +01:00
parent 87e6c6cdee
commit 5f910a0bec
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
11 changed files with 150 additions and 54 deletions

View File

@ -240,10 +240,11 @@
(fx/defn start-public-chat (fx/defn start-public-chat
"Starts a new public chat" "Starts a new public chat"
[cofx topic opts] [cofx topic {:keys [dont-navigate?] :as opts}]
(fx/merge cofx (fx/merge cofx
(add-public-chat topic) (add-public-chat topic)
(navigate-to-chat topic opts) #(when-not dont-navigate?
(navigate-to-chat % topic opts))
(public-chat/join-public-chat topic) (public-chat/join-public-chat topic)
(when platform/desktop? (when platform/desktop?
(desktop.events/change-tab :home)))) (desktop.events/change-tab :home))))

View File

@ -671,7 +671,10 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:chat.ui/start-public-chat :chat.ui/start-public-chat
(fn [cofx [_ topic opts]] (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 (handlers/register-handler-fx
:chat.ui/remove-chat :chat.ui/remove-chat

View File

@ -6,15 +6,18 @@
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.utils.platform :as utils.platform] [status-im.utils.platform :as utils.platform]
[status-im.chat.models :as models.chat]
[status-im.accounts.db :as accounts.db] [status-im.accounts.db :as accounts.db]
[status-im.transport.message.protocol :as protocol] [status-im.transport.message.protocol :as protocol]
[status-im.data-store.installations :as data-store.installations] [status-im.data-store.installations :as data-store.installations]
[status-im.native-module.core :as native-module] [status-im.native-module.core :as native-module]
[status-im.utils.identicon :as identicon] [status-im.utils.identicon :as identicon]
[status-im.data-store.contacts :as data-store.contacts] [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])) [status-im.transport.message.pairing :as transport.pairing]))
(def contact-batch-n 4) (def contact-batch-n 4)
(def max-installations 2)
(defn- parse-response [response-js] (defn- parse-response [response-js]
(-> response-js (-> response-js
@ -94,29 +97,41 @@
(not (get-in db [:pairing/installations installation-id]))) (not (get-in db [:pairing/installations installation-id])))
(fx/merge cofx (fx/merge cofx
(upsert-installation new-installation) (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 %)))))))) (prompt-user-on-new-installation %))))))))
(defn sync-installation-account-message [{:keys [db]}] (defn sync-installation-account-message [{:keys [db]}]
(let [account (-> db (let [account (-> db
:account/account :account/account
(select-keys account-mergeable-keys))] (select-keys account-mergeable-keys))]
(transport.pairing/SyncInstallation. {} account))) (transport.pairing/SyncInstallation. {} account {})))
(defn- contact-batch->sync-installation-message [batch] (defn- contact-batch->sync-installation-message [batch]
(let [contacts-to-sync (reduce (fn [acc {:keys [public-key] :as contact}] (let [contacts-to-sync (reduce (fn [acc {:keys [public-key] :as contact}]
(assoc acc public-key (dissoc contact :photo-path))) (assoc acc public-key (dissoc contact :photo-path)))
{} {}
batch)] 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}] (defn sync-installation-messages [{:keys [db] :as cofx}]
(let [contacts (:contacts/contacts db) (let [contacts (:contacts/contacts db)
contact-batches (partition-all contact-batch-n (->> contacts contact-batches (partition-all contact-batch-n (->> contacts
vals vals
(remove :dapp?)))] (remove :dapp?)))]
(conj (mapv contact-batch->sync-installation-message contact-batches) (concat (mapv contact-batch->sync-installation-message contact-batches)
(sync-installation-account-message cofx))))
[(sync-installation-account-message cofx)]
(chats->sync-installation-messages cofx))))
(defn enable [{:keys [db]} installation-id] (defn enable [{:keys [db]} installation-id]
{:db (assoc-in db {:db (assoc-in db
@ -154,8 +169,12 @@
(native-module/disable-installation installation-id (native-module/disable-installation installation-id
(partial handle-disable-installation-response installation-id))) (partial handle-disable-installation-response installation-id)))
(defn enable-fx [_ installation-id] (defn enable-fx [cofx installation-id]
{:pairing/enable-installation 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] (defn disable-fx [_ installation-id]
{:pairing/disable-installation installation-id}) {:pairing/disable-installation installation-id})
@ -184,6 +203,11 @@
(has-paired-installations? cofx)) (has-paired-installations? cofx))
(protocol/send payload nil 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] (defn send-installation-messages [cofx]
;; The message needs to be broken up in chunks as we hit the whisper size limit ;; The message needs to be broken up in chunks as we hit the whisper size limit
(let [sync-messages (sync-installation-messages cofx) (let [sync-messages (sync-installation-messages cofx)
@ -204,16 +228,20 @@
{} {}
contacts)) 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?])] (let [dev-mode? (get-in db [:account/account :dev-mode?])]
(when (and (config/pairing-enabled? dev-mode?) (when (and (config/pairing-enabled? dev-mode?)
(= sender (accounts.db/current-public-key cofx))) (= sender (accounts.db/current-public-key cofx)))
(let [new-contacts (merge-contacts (:contacts/contacts db) (ensure-photo-path contacts)) (let [new-contacts (merge-contacts (:contacts/contacts db) (ensure-photo-path contacts))
new-account (merge-account (:account/account db) account)] new-account (merge-account (:account/account db) account)]
(fx/merge cofx
{:db (assoc db {:db (assoc db
:contacts/contacts new-contacts :contacts/contacts new-contacts
:account/account new-account) :account/account new-account)
:data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]})))) :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] (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?])] (let [dev-mode? (get-in db [:account/account :dev-mode?])]

View File

@ -31,6 +31,7 @@
(select-keys (select-keys
(get-in cofx [:db :contacts/contacts]) (get-in cofx [:db :contacts/contacts])
[chat-id]) [chat-id])
nil
nil)] nil)]
(fx/merge cofx (fx/merge cofx
(protocol/init-chat {:chat-id chat-id (protocol/init-chat {:chat-id chat-id
@ -47,6 +48,7 @@
(select-keys (select-keys
(get-in cofx [:db :contacts/contacts]) (get-in cofx [:db :contacts/contacts])
[chat-id]) [chat-id])
nil
nil) nil)
success-event [:transport/contact-message-sent chat-id] success-event [:transport/contact-message-sent chat-id]
chat (get-in db [:transport/chats chat-id]) chat (get-in db [:transport/chats chat-id])

View File

@ -12,7 +12,7 @@
(log/warn "failed sync installation validation" (spec/explain :message/pair-installation this))))) (log/warn "failed sync installation validation" (spec/explain :message/pair-installation this)))))
(defrecord SyncInstallation (defrecord SyncInstallation
[contacts account] [contacts account chat]
protocol/StatusMessage protocol/StatusMessage
(validate [this] (validate [this]
(if (spec/valid? :message/sync-installation this) (if (spec/valid? :message/sync-installation this)

View File

@ -86,8 +86,8 @@
(deftype SyncInstallationHandler [] (deftype SyncInstallationHandler []
Object Object
(tag [this v] "p1") (tag [this v] "p1")
(rep [this {:keys [contacts account]}] (rep [this {:keys [contacts account chat]}]
#js [contacts account])) #js [contacts account chat]))
(deftype PairInstallationHandler [] (deftype PairInstallationHandler []
Object Object
@ -154,8 +154,8 @@
(contact/ContactUpdate. name profile-image address fcm-token)) (contact/ContactUpdate. name profile-image address fcm-token))
"g5" (fn [[chat-id membership-updates message]] "g5" (fn [[chat-id membership-updates message]]
(group-chat/GroupMembershipUpdate. chat-id membership-updates message)) (group-chat/GroupMembershipUpdate. chat-id membership-updates message))
"p1" (fn [[contacts account]] "p1" (fn [[contacts account chat]]
(pairing/SyncInstallation. contacts account)) (pairing/SyncInstallation. contacts account chat))
"p2" (fn [[installation-id device-type]] "p2" (fn [[installation-id device-type]]
(pairing/PairInstallation. installation-id device-type))}})) (pairing/PairInstallation. installation-id device-type))}}))

View File

@ -77,11 +77,12 @@
[react/text {:style styles/qr-code-copy-text} [react/text {:style styles/qr-code-copy-text}
(i18n/label :copy-qr)]]]]])) (i18n/label :copy-qr)]]]]]))
(defn installations-section [installations] (defn installations-section [your-installation-id installations]
[react/view [react/view
[pairing.views/pair-this-device] [pairing.views/pair-this-device]
[pairing.views/sync-devices] [pairing.views/sync-devices]
[react/view {:style pairing.styles/installation-list} [react/view {:style pairing.styles/installation-list}
[pairing.views/your-device your-installation-id]
(for [installation installations] (for [installation installations]
^{:key (:installation-id installation)} ^{:key (:installation-id installation)}
[react/view {:style {:margin-bottom 10}} [react/view {:style {:margin-bottom 10}}
@ -176,10 +177,11 @@
[logging-display]]))) [logging-display]])))
(views/defview installations [] (views/defview installations []
(views/letsubs [installations [:pairing/installations]] (views/letsubs [installations [:pairing/installations]
installation-id [:pairing/installation-id]]
[react/scroll-view [react/scroll-view
(when (config/pairing-enabled? true) (when (config/pairing-enabled? true)
(installations-section installations))])) (installations-section installation-id installations))]))
(views/defview backup-recovery-phrase [] (views/defview backup-recovery-phrase []
[profile.recovery/backup-seed]) [profile.recovery/backup-seed])

View File

@ -8,3 +8,7 @@
(->> installations (->> installations
vals vals
(sort-by (comp unchecked-negate :last-paired))))) (sort-by (comp unchecked-negate :last-paired)))))
(re-frame/reg-sub :pairing/installation-id
:<- [:get :account/account]
:installation-id)

View File

@ -2,13 +2,16 @@
(:require-macros [status-im.utils.views :as views]) (:require-macros [status-im.utils.views :as views])
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[reagent.core :as reagent]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.ui.screens.main-tabs.styles :as main-tabs.styles] [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.components.icons.vector-icons :as icons]
[status-im.ui.screens.home.styles :as home.styles] [status-im.ui.screens.home.styles :as home.styles]
[status-im.utils.platform :as utils.platform] [status-im.utils.platform :as utils.platform]
[status-im.utils.gfycat.core :as gfycat] [status-im.utils.gfycat.core :as gfycat]
[status-im.ui.components.button.view :as buttons] [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.list.views :as list]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar] [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.profile.components.views :as profile.components]
[status-im.ui.screens.pairing.styles :as styles])) [status-im.ui.screens.pairing.styles :as styles]))
(def syncing (reagent/atom false))
(defn icon-style [{:keys [width height] :as style}] (defn icon-style [{:keys [width height] :as style}]
(if utils.platform/desktop? (if utils.platform/desktop?
{:container-style {:width width {:container-style {:width width
@ -26,25 +31,37 @@
style)) style))
(defn synchronize-installations! [] (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])) (re-frame/dispatch [:pairing.ui/synchronize-installation-pressed]))
(defn pair! [] (defn pair! []
(re-frame/dispatch [:pairing.ui/pair-devices-pressed])) (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])) (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])) (re-frame/dispatch [:pairing.ui/disable-installation-pressed installation-id]))
(defn footer [] (defn toggle-enabled! [installation-id enabled? _]
[react/touchable-highlight {:on-press synchronize-installations! (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} :style main-tabs.styles/tabs-container}
[react/view [react/view
{:style styles/footer-content} {:style styles/footer-content}
[react/text [react/text
{:style styles/footer-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 [] (defn pair-this-device []
[react/touchable-highlight {:on-press pair! [react/touchable-highlight {:on-press pair!
@ -68,14 +85,30 @@
[icons/icon :icons/wnode (icon-style (styles/pairing-button-icon true))]]] [icons/icon :icons/wnode (icon-style (styles/pairing-button-icon true))]]]
[react/view {:style styles/pairing-actions-text} [react/view {:style styles/pairing-actions-text}
[react/view [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]}] (defn render-row [{:keys [device-type enabled? installation-id]}]
[react/touchable-highlight [react/touchable-highlight
{:on-press (if enabled? {:accessibility-label :installation-item}
(partial disable-installation! installation-id)
(partial enable-installation! installation-id))
:accessibility-label :installation-item}
[react/view {:style styles/installation-item} [react/view {:style styles/installation-item}
[react/view {:style (styles/pairing-button enabled?)} [react/view {:style (styles/pairing-button enabled?)}
[icons/icon (if (= "desktop" [icons/icon (if (= "desktop"
@ -86,28 +119,35 @@
[react/view {:style styles/pairing-actions-text} [react/view {:style styles/pairing-actions-text}
[react/view [react/view
[react/text {:style styles/installation-item-name-text} [react/text {:style styles/installation-item-name-text}
(gfycat/generate-gfy installation-id)]] (gfycat/generate-gfy installation-id)]]]
[react/view [react/view
[react/text {:style styles/installation-status} (if utils.platform/ios?
(if enabled? ;; 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).
(i18n/label :t/syncing-enabled) ;; Replacing therefore with checkbox until I have more time to investigate
(i18n/label :t/syncing-disabled))]]]]]) (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} [react/scroll-view {:style styles/wrapper}
[your-device installation-id]
(when (seq installations)
[list/flat-list {:data installations [list/flat-list {:data installations
:default-separator? false :default-separator? false
:key-fn :installation-id :key-fn :installation-id
:render-fn render-row}]]) :render-fn render-row}])])
(defn installations-list [installations] (defn installations-list [installation-id installations]
[react/view {:style styles/installation-list} [react/view {:style styles/installation-list}
[react/view {:style styles/paired-devices-title} [react/view {:style styles/paired-devices-title}
[react/text (i18n/label :t/paired-devices)]] [react/text (i18n/label :t/paired-devices)]]
(render-rows installations)]) (render-rows installation-id installations)])
(views/defview installations [] (views/defview installations []
(views/letsubs [installations [:pairing/installations]] (views/letsubs [installation-id [:pairing/installation-id]
installations [:pairing/installations]]
[react/view {:flex 1} [react/view {:flex 1}
[status-bar/status-bar] [status-bar/status-bar]
[toolbar/toolbar {} [toolbar/toolbar {}
@ -115,6 +155,5 @@
[toolbar/content-title (i18n/label :t/devices)]] [toolbar/content-title (i18n/label :t/devices)]]
[react/scroll-view {:style {:background-color :white}} [react/scroll-view {:style {:background-color :white}}
[pair-this-device] [pair-this-device]
(when (seq installations) [installations-list installation-id installations]]
[installations-list installations])] (when (seq installations) [footer syncing])]))
(when (seq installations) [footer])]))

View File

@ -153,7 +153,15 @@
(let [cofx {:db {:account/account new-account}} (let [cofx {:db {:account/account new-account}}
sync-message {:account old-account}] sync-message {:account old-account}]
(is (= new-account (get-in (pairing/handle-sync-installation cofx sync-message "us") (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 (deftest handle-pair-installation-test
(with-redefs [config/pairing-enabled? (constantly true)] (with-redefs [config/pairing-enabled? (constantly true)]
@ -183,6 +191,9 @@
:name "name" :name "name"
:photo-path "photo-path" :photo-path "photo-path"
:last-updated 1} :last-updated 1}
:chats {"status" {:public? true
:is-active true
:chat-id "status"}}
:contacts/contacts {"contact-1" {:name "contact-1" :contacts/contacts {"contact-1" {:name "contact-1"
:public-key "contact-1"} :public-key "contact-1"}
"contact-2" {:name "contact-2" "contact-2" {:name "contact-2"
@ -201,12 +212,14 @@
:public-key "contact-3"} :public-key "contact-3"}
"contact-4" {:name "contact-4" "contact-4" {:name "contact-4"
:public-key "contact-4"}} :public-key "contact-4"}}
nil) {} {})
(transport.pairing/SyncInstallation. {"contact-5" {:name "contact-5" (transport.pairing/SyncInstallation. {"contact-5" {:name "contact-5"
:public-key "contact-5"}} nil) :public-key "contact-5"}} {} {})
(transport.pairing/SyncInstallation. {} {:photo-path "photo-path" (transport.pairing/SyncInstallation. {} {:photo-path "photo-path"
:name "name" :name "name"
:last-updated 1})]] :last-updated 1} {})
(transport.pairing/SyncInstallation. {} {} {:public? true
:chat-id "status"})]]
(is (= expected (pairing/sync-installation-messages cofx)))))) (is (= expected (pairing/sync-installation-messages cofx))))))
(deftest handle-bundles-added-test (deftest handle-bundles-added-test

View File

@ -9,10 +9,14 @@
"pair": "Pair devices", "pair": "Pair devices",
"pair-this-device": "Pair this device", "pair-this-device": "Pair this device",
"pair-this-device-description": "Pair your devices to sync contacts and chats between them", "pair-this-device-description": "Pair your devices to sync contacts and chats between them",
"you": "you",
"syncing-enabled": "Syncing enabled", "syncing-enabled": "Syncing enabled",
"syncing-disabled": "Syncing disabled", "syncing-disabled": "Syncing disabled",
"sync-all-devices": "Sync all devices", "sync-all-devices": "Sync all devices",
"syncing-devices": "Syncing...",
"paired-devices": "Paired devices", "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-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-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", "pairing-go-to-installation": "Go to pairing settings",