Enable syncing of contacts & style pairing section

Everytime a contact request is sent/confirmed a sync message is also
sent to other devices so the contact is kept in sync.

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2018-11-08 14:07:29 +01:00
parent e8d3e39063
commit 1104becfba
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
23 changed files with 427 additions and 164 deletions

View File

@ -0,0 +1,3 @@
<svg width="25" height="25" viewBox="0 0 22 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 0C3.34315 0 2 1.34315 2 3C2 4.02458 2.00001 5.04917 2.00002 6.07375C2.00004 8.04917 2.00007 10.0246 2.00001 12H1C0.447715 12 0 12.4477 0 13C0 13.5523 0.447716 14 1 14H1.99992H20H21C21.5523 14 22 13.5523 22 13C22 12.4477 21.5523 12 21 12H20V3C20 1.34315 18.6569 0 17 0H5ZM18 12V3C18 2.44772 17.5523 2 17 2H5C4.44772 2 4 2.44772 4 3V12H18Z" fill="#4360DF"/>
</svg>

After

Width:  |  Height:  |  Size: 511 B

View File

@ -0,0 +1,4 @@
<svg width="25" height="25" viewBox="0 0 14 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 3C0 1.34315 1.34315 0 3 0H11C12.6569 0 14 1.34315 14 3V19C14 20.6569 12.6569 22 11 22H3C1.34315 22 0 20.6569 0 19V3ZM3 2C2.44772 2 2 2.44772 2 3V19C2 19.5523 2.44772 20 3 20H11C11.5523 20 12 19.5523 12 19V3C12 2.44772 11.5523 2 11 2H10.4142C10.149 2 9.89464 2.10536 9.70711 2.29289L9.29289 2.70711C9.10536 2.89464 8.851 3 8.58579 3H5.41421C5.149 3 4.89464 2.89464 4.70711 2.70711L4.29289 2.29289C4.10536 2.10536 3.851 2 3.58579 2H3Z" fill="#4360DF"/>
</svg>

After

Width:  |  Height:  |  Size: 607 B

View File

@ -50,6 +50,9 @@
(and (= constants/content-type-text content-type) (not emoji?)) (and (= constants/content-type-text content-type) (not emoji?))
(update :content message-content/enrich-content)))) (update :content message-content/enrich-content))))
(defn system-message? [{:keys [message-type]}]
(= :system-message message-type))
(fx/defn re-index-message-groups (fx/defn re-index-message-groups
"Relative datemarks of message groups can get obsolete with passing time, "Relative datemarks of message groups can get obsolete with passing time,
this function re-indexes them for given chat" this function re-indexes them for given chat"
@ -83,9 +86,9 @@
status) status)
:data-store/tx [(user-statuses-store/save-status-tx status)]})) :data-store/tx [(user-statuses-store/save-status-tx status)]}))
(defn add-outgoing-status [{:keys [from message-type] :as message} current-public-key] (defn add-outgoing-status [{:keys [from] :as message} current-public-key]
(assoc message :outgoing (and (= from current-public-key) (assoc message :outgoing (and (= from current-public-key)
(not= :system-message message-type)))) (not (system-message? message)))))
(fx/defn add-message (fx/defn add-message
[{:keys [db] :as cofx} batch? {:keys [chat-id message-id clock-value timestamp content from] :as message} current-chat?] [{:keys [db] :as cofx} batch? {:keys [chat-id message-id clock-value timestamp content from] :as message} current-chat?]
@ -105,11 +108,12 @@
;; this will increase last-clock-value twice when sending our own messages ;; this will increase last-clock-value twice when sending our own messages
(update-in [:chats chat-id :last-clock-value] (partial utils.clocks/receive clock-value))) (update-in [:chats chat-id :last-clock-value] (partial utils.clocks/receive clock-value)))
(and (not current-chat?) (and (not current-chat?)
(not= from (:current-public-key db))) (not= from current-public-key))
(update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id)) (update-in [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id))
:data-store/tx [(messages-store/save-message-tx prepared-message)]} :data-store/tx [(messages-store/save-message-tx prepared-message)]}
(when (and platform/desktop? (when (and platform/desktop?
(not batch?)) (not batch?)
(not (system-message? prepared-message)))
(chat-model/update-dock-badge-label)) (chat-model/update-dock-badge-label))
(when-not batch? (when-not batch?
(re-index-message-groups chat-id)) (re-index-message-groups chat-id))

View File

@ -239,6 +239,19 @@
browser/v8 browser/v8
dapp-permissions/v9]) dapp-permissions/v9])
(def v24 [chat/v8
transport/v7
contact/v3
message/v7
mailserver/v11
mailserver-topic/v1
user-status/v2
membership-update/v1
installation/v2
local-storage/v1
browser/v8
dapp-permissions/v9])
;; put schemas ordered by version ;; put schemas ordered by version
(def schemas [{:schema v1 (def schemas [{:schema v1
:schemaVersion 1 :schemaVersion 1
@ -308,4 +321,7 @@
:migration migrations/v22} :migration migrations/v22}
{:schema v23 {:schema v23
:schemaVersion 23 :schemaVersion 23
:migration migrations/v23}]) :migration migrations/v23}
{:schema v24
:schemaVersion 24
:migration migrations/v24}])

View File

@ -153,3 +153,6 @@
new-user-status (aget new-user-statuses i) new-user-status (aget new-user-statuses i)
whisper-identity (aget old-user-status "whisper-identity")] whisper-identity (aget old-user-status "whisper-identity")]
(aset new-user-status "public-key" whisper-identity))))) (aset new-user-status "public-key" whisper-identity)))))
(defn v24 [old-realm new-realm]
(log/debug "migrating v24 account database"))

View File

@ -1115,7 +1115,7 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:pairing.ui/synchronize-installation-pressed :pairing.ui/synchronize-installation-pressed
(fn [cofx _] (fn [cofx _]
(pairing/send-installation-message cofx))) (pairing/send-installation-messages cofx)))
(handlers/register-handler-fx (handlers/register-handler-fx
:pairing.ui/enable-installation-pressed :pairing.ui/enable-installation-pressed

View File

@ -1,16 +1,19 @@
(ns status-im.pairing.core (ns status-im.pairing.core
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[clojure.string :as string]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[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.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.accounts.db :as accounts.db]
[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.transport.message.pairing :as transport.pairing])) [status-im.transport.message.pairing :as transport.pairing]))
(def contact-batch-n 4)
(defn- parse-response [response-js] (defn- parse-response [response-js]
(-> response-js (-> response-js
js/JSON.parse js/JSON.parse
@ -21,6 +24,12 @@
device-type utils.platform/os] device-type utils.platform/os]
(protocol/send (transport.pairing/PairInstallation. installation-id device-type) nil cofx))) (protocol/send (transport.pairing/PairInstallation. installation-id device-type) nil cofx)))
(defn has-paired-installations? [cofx]
(->>
(get-in cofx [:db :pairing/installations])
vals
(some :enabled?)))
(defn send-pair-installation [cofx payload] (defn send-pair-installation [cofx payload]
(let [{:keys [web3]} (:db cofx) (let [{:keys [web3]} (:db cofx)
current-public-key (accounts.db/current-public-key cofx)] current-public-key (accounts.db/current-public-key cofx)]
@ -29,13 +38,9 @@
:payload payload}})) :payload payload}}))
(defn merge-contact [local remote] (defn merge-contact [local remote]
(let [[old-contact new-contact] (sort-by :last-updated [local remote])] (let [[old-contact new-contact] (sort-by :last-updated [remote local])]
(-> local (-> local
(merge new-contact) (merge new-contact)
(assoc :photo-path
(or (:photo-path new-contact)
(:photo-path old-contact)
(identicon/identicon (:public-key local))))
(assoc :pending? (boolean (assoc :pending? (boolean
(and (:pending? local true) (and (:pending? local true)
(:pending? remote true))))))) (:pending? remote true)))))))
@ -66,8 +71,15 @@
(defn sync-installation-messages [{:keys [db]}] (defn sync-installation-messages [{:keys [db]}]
(let [contacts (:contacts/contacts db)] (let [contacts (:contacts/contacts db)]
(map (map
(fn [[k v]] (transport.pairing/SyncInstallation. {k (dissoc v :photo-path)})) (fn [batch]
contacts))) (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)))
(partition-all contact-batch-n (->> contacts
vals
(remove :dapp?))))))
(defn enable [{:keys [db]} installation-id] (defn enable [{:keys [db]} installation-id]
{:db (assoc-in db {:db (assoc-in db
@ -119,29 +131,54 @@
:pairing/disable-installation :pairing/disable-installation
disable-installation!) disable-installation!)
(defn send-installation-message [cofx] (fx/defn send-sync-installation [cofx payload]
;; The message needs to be broken up in chunks as we hit the whisper size limit
(let [{:keys [web3]} (:db cofx) (let [{:keys [web3]} (:db cofx)
current-public-key (accounts.db/current-public-key cofx) current-public-key (accounts.db/current-public-key cofx)]
sync-messages (sync-installation-messages cofx)]
{:shh/send-direct-message {:shh/send-direct-message
(map #(hash-map :web3 web3 [{:web3 web3
:src current-public-key :src current-public-key
:dst current-public-key :dst current-public-key
:payload %) sync-messages)})) :payload payload}]}))
(fx/defn send-installation-message-fx [cofx payload]
(let [dev-mode? (get-in cofx [:db :account/account :dev-mode?])]
(when (and (config/pairing-enabled? dev-mode?)
(has-paired-installations? cofx))
(protocol/send payload nil cofx))))
(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)
sync-messages-fx (map send-installation-message-fx sync-messages)]
(apply fx/merge cofx sync-messages-fx)))
(defn ensure-photo-path
"Make sure a photo path is there, generate otherwise"
[contacts]
(reduce-kv (fn [acc k {:keys [public-key photo-path] :as v}]
(assoc acc k
(assoc
v
:photo-path
(if (string/blank? photo-path)
(identicon/identicon public-key)
photo-path))))
{}
contacts))
(defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts]} sender] (defn handle-sync-installation [{:keys [db] :as cofx} {:keys [contacts]} 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) contacts)] (let [new-contacts (merge-contacts (:contacts/contacts db) (ensure-photo-path contacts))]
{:db (assoc db :contacts/contacts new-contacts) {:db (assoc db :contacts/contacts new-contacts)
:data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]})))) :data-store/tx [(data-store.contacts/save-contacts-tx (vals new-contacts))]}))))
(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?])]
(when (and (config/pairing-enabled? dev-mode?) (when (and (config/pairing-enabled? dev-mode?)
(= sender (get-in cofx [:db :current-public-key])) (= sender (accounts.db/current-public-key cofx))
(not= (get-in db [:account/account :installation-id]) installation-id)) (not= (get-in db [:account/account :installation-id]) installation-id))
(let [installation {:installation-id installation-id (let [installation {:installation-id installation-id
:device-type device-type :device-type device-type

View File

@ -1,7 +1,11 @@
(ns status-im.transport.impl.send (ns status-im.transport.impl.send
(:require [status-im.group-chats.core :as group-chats] (:require [status-im.group-chats.core :as group-chats]
[status-im.utils.fx :as fx]
[status-im.pairing.core :as pairing] [status-im.pairing.core :as pairing]
[status-im.data-store.transport :as transport-store]
[status-im.transport.db :as transport.db]
[status-im.transport.message.pairing :as transport.pairing] [status-im.transport.message.pairing :as transport.pairing]
[status-im.transport.message.contact :as transport.contact]
[status-im.transport.message.group-chat :as transport.group-chat] [status-im.transport.message.group-chat :as transport.group-chat]
[status-im.transport.message.protocol :as protocol])) [status-im.transport.message.protocol :as protocol]))
@ -14,3 +18,45 @@
protocol/StatusMessage protocol/StatusMessage
(send [this _ cofx] (send [this _ cofx]
(pairing/send-pair-installation cofx this))) (pairing/send-pair-installation cofx this)))
(extend-type transport.pairing/SyncInstallation
protocol/StatusMessage
(send [this _ cofx]
(pairing/send-sync-installation cofx this)))
(extend-type transport.contact/ContactRequest
protocol/StatusMessage
(send [this chat-id cofx]
(let [sync-message (transport.pairing/SyncInstallation.
(select-keys
(get-in cofx [:db :contacts/contacts])
[chat-id]))]
(fx/merge cofx
(protocol/init-chat {:chat-id chat-id
:resend? "contact-request"})
(protocol/send-with-pubkey {:chat-id chat-id
:payload this
:success-event [:transport/contact-message-sent chat-id]})
(pairing/send-installation-message-fx sync-message)))))
(extend-type transport.contact/ContactRequestConfirmed
protocol/StatusMessage
(send [this chat-id {:keys [db] :as cofx}]
(let [sync-message (transport.pairing/SyncInstallation.
(select-keys
(get-in cofx [:db :contacts/contacts])
[chat-id]))
success-event [:transport/contact-message-sent chat-id]
chat (get-in db [:transport/chats chat-id])
updated-chat (if chat
(assoc chat :resend? "contact-request-confirmation")
(transport.db/create-chat {:resend? "contact-request-confirmation"}))]
(fx/merge cofx
{:db (assoc-in db
[:transport/chats chat-id] updated-chat)
:data-store/tx [(transport-store/save-transport-tx {:chat-id chat-id
:chat updated-chat})]}
(protocol/send-with-pubkey {:chat-id chat-id
:payload this
:success-event success-event})
(pairing/send-installation-message-fx sync-message)))))

View File

@ -8,33 +8,12 @@
(defrecord ContactRequest [name profile-image address fcm-token] (defrecord ContactRequest [name profile-image address fcm-token]
protocol/StatusMessage protocol/StatusMessage
(send [this chat-id {:keys [db random-id-generator] :as cofx}]
(fx/merge cofx
(protocol/init-chat {:chat-id chat-id
:resend? "contact-request"})
(protocol/send-with-pubkey {:chat-id chat-id
:payload this
:success-event [:transport/contact-message-sent chat-id]})))
(validate [this] (validate [this]
(when (spec/valid? :message/contact-request this) (when (spec/valid? :message/contact-request this)
this))) this)))
(defrecord ContactRequestConfirmed [name profile-image address fcm-token] (defrecord ContactRequestConfirmed [name profile-image address fcm-token]
protocol/StatusMessage protocol/StatusMessage
(send [this chat-id {:keys [db] :as cofx}]
(let [success-event [:transport/contact-message-sent chat-id]
chat (get-in db [:transport/chats chat-id])
updated-chat (if chat
(assoc chat :resend? "contact-request-confirmation")
(transport.db/create-chat {:resend? "contact-request-confirmation"}))]
(fx/merge cofx
{:db (assoc-in db
[:transport/chats chat-id] updated-chat)
:data-store/tx [(transport-store/save-transport-tx {:chat-id chat-id
:chat updated-chat})]}
(protocol/send-with-pubkey {:chat-id chat-id
:payload this
:success-event success-event}))))
(validate [this] (validate [this]
(when (spec/valid? :message/contact-request-confirmed this) (when (spec/valid? :message/contact-request-confirmed this)
this))) this)))
@ -79,15 +58,3 @@
{:shh/remove-filter {:chat-id chat-id {:shh/remove-filter {:chat-id chat-id
:filter filter}})) :filter filter}}))
(defrecord NewContactKey [sym-key topic message]
protocol/StatusMessage
(send
;; no-op, we don't send NewContactKey anymore
[this chat-id cofx])
(receive
;;for compatibility with old clients, we only care about the message within
[this chat-id _ timestamp {:keys [db] :as cofx}]
(protocol/receive message chat-id chat-id timestamp cofx))
(validate [this]
(when (spec/valid? :message/new-contact-key this)
this)))

View File

@ -82,7 +82,7 @@
StatusMessage StatusMessage
(send [this chat-id cofx] (send [this chat-id cofx]
(let [dev-mode? (get-in cofx [:db :account/account :dev-mode?]) (let [dev-mode? (get-in cofx [:db :account/account :dev-mode?])
current-public-key (get-in cofx [:db :current-public-key]) current-public-key (accounts.db/current-public-key cofx)
params {:chat-id chat-id params {:chat-id chat-id
:payload this :payload this
:success-event [:transport/message-sent :success-event [:transport/message-sent

View File

@ -21,11 +21,6 @@
;; The tag will determine which reader is used to recreate the clojure record ;; The tag will determine which reader is used to recreate the clojure record
;; When migrating a particular record, it is important to use a different type and still handle the previous ;; When migrating a particular record, it is important to use a different type and still handle the previous
;; gracefully for compatibility ;; gracefully for compatibility
(deftype NewContactKeyHandler []
Object
(tag [this v] "c1")
(rep [this {:keys [sym-key topic message]}]
#js [sym-key topic message]))
(deftype ContactRequestHandler [] (deftype ContactRequestHandler []
Object Object
@ -102,8 +97,7 @@
(def writer (transit/writer :json (def writer (transit/writer :json
{:handlers {:handlers
{contact/NewContactKey (NewContactKeyHandler.) {contact/ContactRequest (ContactRequestHandler.)
contact/ContactRequest (ContactRequestHandler.)
contact/ContactRequestConfirmed (ContactRequestConfirmedHandler.) contact/ContactRequestConfirmed (ContactRequestConfirmedHandler.)
contact/ContactUpdate (ContactUpdateHandler.) contact/ContactUpdate (ContactUpdateHandler.)
protocol/Message (MessageHandler.) protocol/Message (MessageHandler.)
@ -145,9 +139,7 @@
;; Here we only need to call the record with the arguments parsed from the clojure datastructures ;; Here we only need to call the record with the arguments parsed from the clojure datastructures
(def reader (transit/reader :json (def reader (transit/reader :json
{:handlers {:handlers
{"c1" (fn [[sym-key topic message]] {"c2" (fn [[name profile-image address fcm-token]]
(contact/NewContactKey. sym-key topic message))
"c2" (fn [[name profile-image address fcm-token]]
(contact/ContactRequest. name profile-image address fcm-token)) (contact/ContactRequest. name profile-image address fcm-token))
"c3" (fn [[name profile-image address fcm-token]] "c3" (fn [[name profile-image address fcm-token]]
(contact/ContactRequestConfirmed. name profile-image address fcm-token)) (contact/ContactRequestConfirmed. name profile-image address fcm-token))

View File

@ -42,6 +42,9 @@
{:font-weight :normal {:font-weight :normal
:color colors/white :color colors/white
:padding-horizontal 16 :padding-horizontal 16
:desktop {:font-size 14
:padding-vertical 10
:letter-spacing 0.5}
:android {:font-size 14 :android {:font-size 14
:padding-vertical 10 :padding-vertical 10
:letter-spacing 0.5} :letter-spacing 0.5}

View File

@ -58,7 +58,7 @@
[react/view styles/more-btn [react/view styles/more-btn
[vector-icons/icon :icons/options {:accessibility-label :options}]]]]))]]) [vector-icons/icon :icons/options {:accessibility-label :options}]]]]))]])
(views/defview toogle-contact-view [{:keys [public-key] :as contact} selected-key on-toggle-handler] (views/defview toggle-contact-view [{:keys [public-key] :as contact} selected-key on-toggle-handler]
(views/letsubs [checked [selected-key public-key]] (views/letsubs [checked [selected-key public-key]]
[react/view {:accessibility-label :contact-item} [react/view {:accessibility-label :contact-item}
[list/list-item-with-checkbox [list/list-item-with-checkbox

View File

@ -54,6 +54,7 @@
:icons/close (js/require "./resources/icons/close.svg") :icons/close (js/require "./resources/icons/close.svg")
:icons/copy-from (js/require "./resources/icons/copy_from.svg") :icons/copy-from (js/require "./resources/icons/copy_from.svg")
:icons/delete (js/require "./resources/icons/delete.svg") :icons/delete (js/require "./resources/icons/delete.svg")
:icons/desktop (js/require "./resources/icons/desktop.svg")
:icons/dots-horizontal (js/require "./resources/icons/dots_horizontal.svg") :icons/dots-horizontal (js/require "./resources/icons/dots_horizontal.svg")
:icons/dots-vertical (js/require "./resources/icons/dots_vertical.svg") :icons/dots-vertical (js/require "./resources/icons/dots_vertical.svg")
:icons/exclamation-mark (js/require "./resources/icons/exclamation_mark.svg") :icons/exclamation-mark (js/require "./resources/icons/exclamation_mark.svg")
@ -67,6 +68,7 @@
:icons/in-contacts (js/require "./resources/icons/in_contacts.svg") :icons/in-contacts (js/require "./resources/icons/in_contacts.svg")
:icons/lock (js/require "./resources/icons/lock.svg") :icons/lock (js/require "./resources/icons/lock.svg")
:icons/mic (js/require "./resources/icons/mic.svg") :icons/mic (js/require "./resources/icons/mic.svg")
:icons/mobile (js/require "./resources/icons/mobile.svg")
:icons/ok (js/require "./resources/icons/ok.svg") :icons/ok (js/require "./resources/icons/ok.svg")
:icons/public (js/require "./resources/icons/public.svg") :icons/public (js/require "./resources/icons/public.svg")
:icons/public-chat (js/require "./resources/icons/public_chat.svg") :icons/public-chat (js/require "./resources/icons/public_chat.svg")
@ -125,6 +127,7 @@
:icons/close (components.svg/slurp-svg "./resources/icons/close.svg") :icons/close (components.svg/slurp-svg "./resources/icons/close.svg")
:icons/copy-from (components.svg/slurp-svg "./resources/icons/copy_from.svg") :icons/copy-from (components.svg/slurp-svg "./resources/icons/copy_from.svg")
:icons/delete (components.svg/slurp-svg "./resources/icons/delete.svg") :icons/delete (components.svg/slurp-svg "./resources/icons/delete.svg")
:icons/desktop (components.svg/slurp-svg "./resources/icons/desktop.svg")
:icons/dots-horizontal (components.svg/slurp-svg "./resources/icons/dots_horizontal.svg") :icons/dots-horizontal (components.svg/slurp-svg "./resources/icons/dots_horizontal.svg")
:icons/dots-vertical (components.svg/slurp-svg "./resources/icons/dots_vertical.svg") :icons/dots-vertical (components.svg/slurp-svg "./resources/icons/dots_vertical.svg")
:icons/exclamation-mark (components.svg/slurp-svg "./resources/icons/exclamation_mark.svg") :icons/exclamation-mark (components.svg/slurp-svg "./resources/icons/exclamation_mark.svg")
@ -139,6 +142,7 @@
:icons/lock (components.svg/slurp-svg "./resources/icons/lock.svg") :icons/lock (components.svg/slurp-svg "./resources/icons/lock.svg")
:icons/lock-opened (components.svg/slurp-svg "./resources/icons/lock_opened.svg") :icons/lock-opened (components.svg/slurp-svg "./resources/icons/lock_opened.svg")
:icons/mic (components.svg/slurp-svg "./resources/icons/mic.svg") :icons/mic (components.svg/slurp-svg "./resources/icons/mic.svg")
:icons/mobile (components.svg/slurp-svg "./resources/icons/mobile.svg")
:icons/ok (components.svg/slurp-svg "./resources/icons/ok.svg") :icons/ok (components.svg/slurp-svg "./resources/icons/ok.svg")
:icons/public (components.svg/slurp-svg "./resources/icons/public.svg") :icons/public (components.svg/slurp-svg "./resources/icons/public.svg")
:icons/public-chat (components.svg/slurp-svg "./resources/icons/public_chat.svg") :icons/public-chat (components.svg/slurp-svg "./resources/icons/public_chat.svg")

View File

@ -80,13 +80,9 @@
[react/view [react/view
[react/view {:style styles/title-separator}] [react/view {:style styles/title-separator}]
[react/text {:style styles/mailserver-title} (i18n/label :devices)] [react/text {:style styles/mailserver-title} (i18n/label :devices)]
[react/touchable-highlight {:style styles/pair-button [pairing.views/pair-this-device]
:on-press pairing.views/pair!} [pairing.views/sync-devices]
[react/text (i18n/label :pair)]] [pairing.views/installations-list installations]])
(for [installation installations]
^{:key (:installation-id installation)}
[react/view {:style {:margin-vertical 8}}
[pairing.views/render-row installation]])])
(views/defview advanced-settings [] (views/defview advanced-settings []
(views/letsubs [installations [:pairing/installations] (views/letsubs [installations [:pairing/installations]

View File

@ -2,8 +2,11 @@
(:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.contact.contact :refer [toogle-contact-view]] [status-im.utils.platform :as utils.platform]
[status-im.ui.components.button.view :as buttons]
[status-im.ui.components.contact.contact :refer [toggle-contact-view]]
[status-im.ui.components.list.views :as list] [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.react :as react]
[status-im.ui.components.status-bar.view :refer [status-bar]] [status-im.ui.components.status-bar.view :refer [status-bar]]
[status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.components.toolbar.view :as toolbar]
@ -18,10 +21,15 @@
(re-frame/dispatch [action public-key]))) (re-frame/dispatch [action public-key])))
(defn- group-toggle-contact [contact] (defn- group-toggle-contact [contact]
[toogle-contact-view contact :is-contact-selected? on-toggle]) [toggle-contact-view contact :is-contact-selected? on-toggle])
(defn- group-toggle-participant [contact] (defn- group-toggle-participant [contact]
[toogle-contact-view contact :is-participant-selected? on-toggle-participant]) [toggle-contact-view contact :is-participant-selected? on-toggle-participant])
(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] (defn- toggle-list-toolbar [{:keys [handler count label]} title]
[toolbar/toolbar {} [toolbar/toolbar {}
@ -33,25 +41,39 @@
label])]) label])])
(defn toggle-list [contacts render-function] (defn toggle-list [contacts render-function]
[react/view {:flex 1} [react/scroll-view {:flex 1}
[list/flat-list {:style styles/contacts-list (if utils.platform/desktop?
:data contacts (for [contact contacts]
:key-fn :address ^{:key (:public-key contact)}
:render-fn render-function (render-function contact))
:keyboardShouldPersistTaps :always}]]) [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)]])
;; Start group chat ;; Start group chat
(defview contact-toggle-list [] (defview contact-toggle-list []
(letsubs [contacts [:all-added-people-contacts] (letsubs [contacts [:all-added-people-contacts]
selected-contacts-count [:selected-contacts-count]] selected-contacts-count [:selected-contacts-count]]
(when (seq contacts) [react/keyboard-avoiding-view {:style styles/group-container}
[react/keyboard-avoiding-view {:style styles/group-container} [status-bar]
[status-bar] [toggle-list-toolbar {:handler #(re-frame/dispatch [:navigate-to :new-group])
[toggle-list-toolbar {:handler #(re-frame/dispatch [:navigate-to :new-group]) :label (i18n/label :t/next)
:label (i18n/label :t/next) :count (pos? selected-contacts-count)}
:count (pos? selected-contacts-count)} (i18n/label :t/group-chat)]
(i18n/label :t/group-chat)] (if (seq contacts)
[toggle-list contacts group-toggle-contact]]))) [toggle-list contacts group-toggle-contact]
[no-contacts])]))
;; Add participants to existing group chat ;; Add participants to existing group chat
(defview add-participants-toggle-list [] (defview add-participants-toggle-list []

View File

@ -17,3 +17,9 @@
(def contacts-list (def contacts-list
{:background-color colors/white}) {:background-color colors/white})
(def no-contact-text
{:margin-bottom 20
:margin-horizontal 50
:text-align :center
:color colors/gray})

View File

@ -1,27 +1,78 @@
(ns status-im.ui.screens.pairing.styles (ns status-im.ui.screens.pairing.styles
(:require [status-im.ui.components.colors :as colors]) (:require [status-im.ui.components.colors :as colors])
(:require-macros [status-im.utils.styles :refer [defstyle]])) (:require-macros [status-im.utils.styles :refer [defnstyle defstyle]]))
(def wrapper (def wrapper
{:flex 1 {:flex 1
:background-color :white}) :background-color :white})
(def installation-item-inner (def installation-item-inner
{:flex 1 {:flex 1
:flex-direction :row :flex-direction :row})
:padding-horizontal 16})
(defstyle installation-item (defstyle installation-item
{:flex-direction :row {:flex-direction :row
:background-color :white :background-color :white
:align-items :center :align-items :center
:padding-horizontal 16 :ios {:height 64}
:ios {:height 64} :android {:height 56}})
:android {:height 56}})
(defstyle installation-item-name-text (defstyle installation-item-name-text
{:color colors/black {:color colors/black})
:ios {:font-size 17
:letter-spacing -0.2 (def installation-list
:line-height 20} {:background-color :white
:android {:font-size 16}}) :padding-horizontal 16
:flex 1})
(def footer-content {:justify-content :center
:flex 1
:align-items :center})
(def footer-text {:color colors/blue
:text-align :center})
(def pair-this-device
{:height 80
:padding-horizontal 16
:padding-top 12
:background-color :white})
(def pair-this-device-actions
{:flex 1
:flex-direction :row})
(defn pairing-button [enabled?]
{:width 40
:height 40
:background-color (if enabled?
colors/blue-light
colors/gray-lighter)
:border-radius 28
:align-items :center
:justify-content :center})
(def installation-status
{:color colors/gray})
(def pairing-actions-text
{:flex 1
:font-size 15
:margin-left 16})
(def pair-this-device-title
{:color colors/blue
:margin-bottom 6
:font-size 15})
(defnstyle pairing-button-icon [enabled?]
(let [color (if enabled?
colors/blue
colors/gray)]
{:desktop {:tint-color color}
:ios {:color color}
:android {:color color}}))
(def paired-devices-title
{:color colors/gray
:margin-vertical 10})

View File

@ -4,4 +4,7 @@
(re-frame/reg-sub :pairing/installations (re-frame/reg-sub :pairing/installations
:<- [:get :pairing/installations] :<- [:get :pairing/installations]
vals) (fn [k]
(->> k
vals
(filter :device-type))))

View File

@ -3,10 +3,12 @@
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[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.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.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.colors :as colors]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[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]
@ -15,8 +17,16 @@
[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]))
(defn synchronize-installation! [id] (defn icon-style [{:keys [width height] :as style}]
#_(re-frame/dispatch [:pairing.ui/synchronize-installation-pressed id])) (if utils.platform/desktop?
{:container-style {:width width
:height height}
:style style}
style))
(defn synchronize-installations! []
(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]))
@ -27,39 +37,84 @@
(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 []
[react/touchable-highlight {:on-press 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)]]])
(defn pair-this-device []
[react/touchable-highlight {:on-press pair!
:style styles/pair-this-device}
[react/view {:style styles/pair-this-device-actions}
[react/view
[react/view (styles/pairing-button true)
[icons/icon :icons/add (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/pair-this-device)]]
[react/view
[react/text (i18n/label :t/pair-this-device-description)]]]]])
(defn sync-devices []
[react/touchable-highlight {:on-press synchronize-installations!
:style styles/pair-this-device}
[react/view {:style styles/pair-this-device-actions}
[react/view
[react/view (styles/pairing-button true)
[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)]]]]])
(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 #(synchronize-installation! installation-id) {:on-press (if enabled?
(partial disable-installation! installation-id)
(partial enable-installation! installation-id))
:accessibility-label :installation-item} :accessibility-label :installation-item}
[react/view styles/installation-item [react/view {:style styles/installation-item}
[react/view styles/installation-item-inner [react/view {:style (styles/pairing-button enabled?)}
[icons/icon (if (= "desktop"
device-type)
:icons/desktop
:icons/mobile)
(icon-style (styles/pairing-button-icon enabled?))]]
[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}
(str (gfycat/generate-gfy installation-id) " - " (or device-type (gfycat/generate-gfy installation-id)]]
"unknown"))]]
[react/view [react/view
(if enabled? [react/text {:style styles/installation-status}
[buttons/primary-button (if enabled?
{:on-press (partial disable-installation! installation-id)} (i18n/label :t/syncing-enabled)
(i18n/label :t/enabled)] (i18n/label :t/syncing-disabled))]]]]])
[buttons/secondary-button
{:on-press (partial enable-installation! installation-id)}
(i18n/label :t/disabled)])]]]])
(defn render-rows [installations] (defn render-rows [installations]
[react/view styles/wrapper [react/view {:style styles/wrapper}
[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]
[react/view {:style styles/installation-list}
[react/view {:style styles/paired-devices-title}
[react/text (i18n/label :t/paired-devices)]]
(render-rows installations)])
(views/defview installations [] (views/defview installations []
(views/letsubs [installations [:pairing/installations]] (views/letsubs [installations [:pairing/installations]]
[react/view {:flex 1} [react/view {:flex 1}
[status-bar/status-bar] [status-bar/status-bar]
[toolbar/toolbar {} [toolbar/toolbar {}
toolbar/default-nav-back toolbar/default-nav-back
[toolbar/content-title (i18n/label :t/devices)] [toolbar/content-title (i18n/label :t/devices)]]
[toolbar/actions [react/scroll-view {:style {:background-color :white}}
[(toolbar.actions/add false pair!)]]] [pair-this-device]
(render-rows installations)])) (when (seq installations)
[installations-list installations])]
(when (seq installations) [footer])]))

View File

@ -12,6 +12,7 @@
(def ^:private mergable-keys (def ^:private mergable-keys
#{:data-store/tx :data-store/base-tx :chat-received-message/add-fx #{:data-store/tx :data-store/base-tx :chat-received-message/add-fx
:shh/add-new-sym-keys :shh/get-new-sym-keys :shh/post :shh/add-new-sym-keys :shh/get-new-sym-keys :shh/post
:shh/send-direct-message
:shh/generate-sym-key-from-password :transport/confirm-messages-processed :shh/generate-sym-key-from-password :transport/confirm-messages-processed
:group-chats/extract-membership-signature :utils/dispatch-later}) :group-chats/extract-membership-signature :utils/dispatch-later})

View File

@ -1,6 +1,7 @@
(ns status-im.test.pairing.core (ns status-im.test.pairing.core
(:require [cljs.test :refer-macros [deftest is testing]] (:require [cljs.test :refer-macros [deftest is testing]]
[status-im.transport.message.pairing :as transport.pairing] [status-im.transport.message.pairing :as transport.pairing]
[status-im.utils.identicon :as identicon]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.pairing.core :as pairing])) [status-im.pairing.core :as pairing]))
@ -72,40 +73,60 @@
(is (= expected (pairing/merge-contact contact-1 contact-2)))))) (is (= expected (pairing/merge-contact contact-1 contact-2))))))
(deftest handle-sync-installation-test (deftest handle-sync-installation-test
(with-redefs [config/pairing-enabled? (constantly true)] (with-redefs [config/pairing-enabled? (constantly true)
(let [old-contact-1 {:name "old-contact-one" identicon/identicon (constantly "generated")]
:last-updated 0 (let [old-contact-1 {:name "old-contact-one"
:photo-path "old-contact-1" :public-key "contact-1"
:pending? true} :last-updated 0
new-contact-1 {:name "new-contact-one" :photo-path "old-contact-1"
:last-updated 1 :pending? true}
:photo-path "new-contact-1" new-contact-1 {:name "new-contact-one"
:pending? false} :public-key "contact-1"
old-contact-2 {:name "old-contact-2" :last-updated 1
:last-updated 0 :photo-path "new-contact-1"
:photo-path "old-contact-2" :pending? false}
:pending? false} old-contact-2 {:name "old-contact-2"
new-contact-2 {:name "new-contact-2" :public-key "contact-2"
:last-updated 1 :last-updated 0
:photo-path "new-contact-2" :photo-path "old-contact-2"
:pending? false} :pending? false}
contact-3 {:name "contact-3" new-contact-2 {:name "new-contact-2"
:photo-path "contact-3" :public-key "contact-2"
:pending? false} :last-updated 1
contact-4 {:name "contact-4" :photo-path "new-contact-2"
:photo-path "contact-4" :pending? false}
:pending? true} contact-3 {:name "contact-3"
:public-key "contact-3"
:photo-path "contact-3"
:pending? false}
contact-4 {:name "contact-4"
:public-key "contact-4"
:pending? true}
local-contact-5 {:name "contact-5"
:photo-path "local"
:public-key "contact-5"
:pending? true
:last-updated 1}
remote-contact-5 {:name "contact-5"
:public-key "contact-5"
:photo-path "remote"
:pending? true
:last-updated 1}
cofx {:db {:account/account {:public-key "us"} cofx {:db {:account/account {:public-key "us"}
:contacts/contacts {"contact-1" old-contact-1 :contacts/contacts {"contact-1" old-contact-1
"contact-2" new-contact-2 "contact-2" new-contact-2
"contact-3" contact-3}}} "contact-3" contact-3
"contact-5" local-contact-5}}}
sync-message {:contacts {"contact-1" new-contact-1 sync-message {:contacts {"contact-1" new-contact-1
"contact-2" old-contact-2 "contact-2" old-contact-2
"contact-4" contact-4}} "contact-4" contact-4
"contact-5" remote-contact-5}}
expected {"contact-1" new-contact-1 expected {"contact-1" new-contact-1
"contact-2" new-contact-2 "contact-2" new-contact-2
"contact-3" contact-3 "contact-3" contact-3
"contact-4" contact-4}] "contact-4" (assoc contact-4
:photo-path "generated")
"contact-5" local-contact-5}]
(testing "not coming from us" (testing "not coming from us"
(is (not (pairing/handle-sync-installation cofx sync-message "not-us")))) (is (not (pairing/handle-sync-installation cofx sync-message "not-us"))))
(testing "coming from us" (testing "coming from us"
@ -115,7 +136,7 @@
(deftest handle-pair-installation-test (deftest handle-pair-installation-test
(with-redefs [config/pairing-enabled? (constantly true)] (with-redefs [config/pairing-enabled? (constantly true)]
(let [cofx {:db {:current-public-key "us" (let [cofx {:db {:account/account {:public-key "us"}
:pairing/installations {"1" {:has-bundle? true :pairing/installations {"1" {:has-bundle? true
:installation-id "1"} :installation-id "1"}
"2" {:has-bundle? false "2" {:has-bundle? false
@ -138,10 +159,26 @@
(deftest sync-installation-messages-test (deftest sync-installation-messages-test
(testing "it creates a sync installation message" (testing "it creates a sync installation message"
(let [cofx {:db {:account/account {:public-key "us"} (let [cofx {:db {:account/account {:public-key "us"}
:contacts/contacts {"contact-1" {:name "contact-1"} :contacts/contacts {"contact-1" {:name "contact-1"
"contact-2" {:name "contact-2"}}}} :public-key "contact-1"}
expected [(transport.pairing/SyncInstallation. {"contact-1" {:name "contact-1"}}) "contact-2" {:name "contact-2"
(transport.pairing/SyncInstallation. {"contact-2" {:name "contact-2"}})]] :public-key "contact-2"}
"contact-3" {:name "contact-3"
:public-key "contact-3"}
"contact-4" {:name "contact-4"
:public-key "contact-4"}
"contact-5" {:name "contact-5"
:public-key "contact-5"}}}}
expected [(transport.pairing/SyncInstallation. {"contact-1" {:name "contact-1"
:public-key "contact-1"}
"contact-2" {:name "contact-2"
:public-key "contact-2"}
"contact-3" {:name "contact-3"
:public-key "contact-3"}
"contact-4" {:name "contact-4"
:public-key "contact-4"}})
(transport.pairing/SyncInstallation. {"contact-5" {:name "contact-5"
:public-key "contact-5"}})]]
(is (= expected (pairing/sync-installation-messages cofx)))))) (is (= expected (pairing/sync-installation-messages cofx))))))
(deftest handle-bundles-added-test (deftest handle-bundles-added-test
@ -162,3 +199,11 @@
(testing "not from us" (testing "not from us"
(let [new-installation {:identity "not-us" :installationID "does-not-matter"}] (let [new-installation {:identity "not-us" :installationID "does-not-matter"}]
(is (not (pairing/handle-bundles-added cofx new-installation)))))))) (is (not (pairing/handle-bundles-added cofx new-installation))))))))
(deftest has-paired-installations-test
(testing "no paired devices"
(is (not (pairing/has-paired-installations? {:db {:pairing/installations {"1" {}
"2" {}}}}))))
(testing "has paired devices"
(is (pairing/has-paired-installations? {:db {:pairing/installations {"1" {}
"2" {:enabled? true}}}}))))

View File

@ -6,8 +6,12 @@
"description": "Description", "description": "Description",
"devices": "Devices", "devices": "Devices",
"pair": "Pair devices", "pair": "Pair devices",
"enabled": "enabled", "pair-this-device": "Pair this device",
"disabled": "disabled", "pair-this-device-description": "Pair your devices to sync contacts and chats between them",
"syncing-enabled": "Syncing enabled",
"syncing-disabled": "Syncing disabled",
"sync-all-devices": "Sync all devices",
"paired-devices": "Paired devices",
"currency-display-name-tzs": "Tanzanian Shilling", "currency-display-name-tzs": "Tanzanian Shilling",
"currency-display-name-brl": "Brazil Real", "currency-display-name-brl": "Brazil Real",
"mainnet-network": "Main network", "mainnet-network": "Main network",
@ -25,6 +29,7 @@
"group-chat-name-changed": "*{{member}}* changed the group's name to *{{name}}*", "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}}* joined the group",
"group-chat-member-removed": "*{{member}}* left the group", "group-chat-member-removed": "*{{member}}* left the group",
"group-chat-no-contacts": "You don't have any contacts yet.\nInvite your friends to start chatting",
"agree-by-continuing": "By continuing you agree\n to our ", "agree-by-continuing": "By continuing you agree\n to our ",
"wallet-advanced": "Advanced", "wallet-advanced": "Advanced",
"currency-display-name-sos": "Somalia Shilling", "currency-display-name-sos": "Somalia Shilling",