diff --git a/resources/icons/desktop.svg b/resources/icons/desktop.svg new file mode 100644 index 0000000000..865a48abf7 --- /dev/null +++ b/resources/icons/desktop.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/icons/mobile.svg b/resources/icons/mobile.svg new file mode 100644 index 0000000000..af01106d41 --- /dev/null +++ b/resources/icons/mobile.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/status_im/chat/models/message.cljs b/src/status_im/chat/models/message.cljs index 9f1915498a..54f3ab6a73 100644 --- a/src/status_im/chat/models/message.cljs +++ b/src/status_im/chat/models/message.cljs @@ -50,6 +50,9 @@ (and (= constants/content-type-text content-type) (not emoji?)) (update :content message-content/enrich-content)))) +(defn system-message? [{:keys [message-type]}] + (= :system-message message-type)) + (fx/defn re-index-message-groups "Relative datemarks of message groups can get obsolete with passing time, this function re-indexes them for given chat" @@ -83,9 +86,9 @@ 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) - (not= :system-message message-type)))) + (not (system-message? message))))) (fx/defn add-message [{: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 (update-in [:chats chat-id :last-clock-value] (partial utils.clocks/receive clock-value))) (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)) :data-store/tx [(messages-store/save-message-tx prepared-message)]} (when (and platform/desktop? - (not batch?)) + (not batch?) + (not (system-message? prepared-message))) (chat-model/update-dock-badge-label)) (when-not batch? (re-index-message-groups chat-id)) 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 6373a064dd..be72368769 100644 --- a/src/status_im/data_store/realm/schemas/account/core.cljs +++ b/src/status_im/data_store/realm/schemas/account/core.cljs @@ -239,6 +239,19 @@ browser/v8 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 (def schemas [{:schema v1 :schemaVersion 1 @@ -308,4 +321,7 @@ :migration migrations/v22} {:schema v23 :schemaVersion 23 - :migration migrations/v23}]) + :migration migrations/v23} + {:schema v24 + :schemaVersion 24 + :migration migrations/v24}]) 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 595f80e828..e2108649df 100644 --- a/src/status_im/data_store/realm/schemas/account/migrations.cljs +++ b/src/status_im/data_store/realm/schemas/account/migrations.cljs @@ -153,3 +153,6 @@ new-user-status (aget new-user-statuses i) whisper-identity (aget old-user-status "whisper-identity")] (aset new-user-status "public-key" whisper-identity))))) + +(defn v24 [old-realm new-realm] + (log/debug "migrating v24 account database")) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 7fee3180ae..e4e80ad663 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -1115,7 +1115,7 @@ (handlers/register-handler-fx :pairing.ui/synchronize-installation-pressed (fn [cofx _] - (pairing/send-installation-message cofx))) + (pairing/send-installation-messages cofx))) (handlers/register-handler-fx :pairing.ui/enable-installation-pressed diff --git a/src/status_im/pairing/core.cljs b/src/status_im/pairing/core.cljs index 169488f702..eb5d99e57d 100644 --- a/src/status_im/pairing/core.cljs +++ b/src/status_im/pairing/core.cljs @@ -1,16 +1,19 @@ (ns status-im.pairing.core (:require [re-frame.core :as re-frame] + [clojure.string :as string] [status-im.utils.fx :as fx] [status-im.utils.config :as config] [status-im.utils.platform :as utils.platform] + [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.accounts.db :as accounts.db] [status-im.utils.identicon :as identicon] [status-im.data-store.contacts :as data-store.contacts] [status-im.transport.message.pairing :as transport.pairing])) +(def contact-batch-n 4) + (defn- parse-response [response-js] (-> response-js js/JSON.parse @@ -21,6 +24,12 @@ device-type utils.platform/os] (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] (let [{:keys [web3]} (:db cofx) current-public-key (accounts.db/current-public-key cofx)] @@ -29,13 +38,9 @@ :payload payload}})) (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 (merge new-contact) - (assoc :photo-path - (or (:photo-path new-contact) - (:photo-path old-contact) - (identicon/identicon (:public-key local)))) (assoc :pending? (boolean (and (:pending? local true) (:pending? remote true))))))) @@ -66,8 +71,15 @@ (defn sync-installation-messages [{:keys [db]}] (let [contacts (:contacts/contacts db)] (map - (fn [[k v]] (transport.pairing/SyncInstallation. {k (dissoc v :photo-path)})) - contacts))) + (fn [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))) + (partition-all contact-batch-n (->> contacts + vals + (remove :dapp?)))))) (defn enable [{:keys [db]} installation-id] {:db (assoc-in db @@ -119,29 +131,54 @@ :pairing/disable-installation disable-installation!) -(defn send-installation-message [cofx] - ;; The message needs to be broken up in chunks as we hit the whisper size limit +(fx/defn send-sync-installation [cofx payload] (let [{:keys [web3]} (:db cofx) - current-public-key (accounts.db/current-public-key cofx) - sync-messages (sync-installation-messages cofx)] + current-public-key (accounts.db/current-public-key cofx)] + {:shh/send-direct-message - (map #(hash-map :web3 web3 - :src current-public-key - :dst current-public-key - :payload %) sync-messages)})) + [{:web3 web3 + :src current-public-key + :dst current-public-key + :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] (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) contacts)] + (let [new-contacts (merge-contacts (:contacts/contacts db) (ensure-photo-path contacts))] {:db (assoc db :contacts/contacts 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] (let [dev-mode? (get-in db [:account/account :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)) (let [installation {:installation-id installation-id :device-type device-type diff --git a/src/status_im/transport/impl/send.cljs b/src/status_im/transport/impl/send.cljs index 68cec39c96..0663a8f78e 100644 --- a/src/status_im/transport/impl/send.cljs +++ b/src/status_im/transport/impl/send.cljs @@ -1,7 +1,11 @@ (ns status-im.transport.impl.send (:require [status-im.group-chats.core :as group-chats] + [status-im.utils.fx :as fx] [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.contact :as transport.contact] [status-im.transport.message.group-chat :as transport.group-chat] [status-im.transport.message.protocol :as protocol])) @@ -14,3 +18,45 @@ protocol/StatusMessage (send [this _ cofx] (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))))) diff --git a/src/status_im/transport/message/contact.cljs b/src/status_im/transport/message/contact.cljs index bbdb545c5b..17ed3338fa 100644 --- a/src/status_im/transport/message/contact.cljs +++ b/src/status_im/transport/message/contact.cljs @@ -8,33 +8,12 @@ (defrecord ContactRequest [name profile-image address fcm-token] 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] (when (spec/valid? :message/contact-request this) this))) (defrecord ContactRequestConfirmed [name profile-image address fcm-token] 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] (when (spec/valid? :message/contact-request-confirmed this) this))) @@ -79,15 +58,3 @@ {:shh/remove-filter {:chat-id chat-id :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))) diff --git a/src/status_im/transport/message/protocol.cljs b/src/status_im/transport/message/protocol.cljs index ac47aecc89..018496e7f4 100644 --- a/src/status_im/transport/message/protocol.cljs +++ b/src/status_im/transport/message/protocol.cljs @@ -82,7 +82,7 @@ StatusMessage (send [this chat-id cofx] (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 :payload this :success-event [:transport/message-sent diff --git a/src/status_im/transport/message/transit.cljs b/src/status_im/transport/message/transit.cljs index 88c95a378d..8474502c64 100644 --- a/src/status_im/transport/message/transit.cljs +++ b/src/status_im/transport/message/transit.cljs @@ -21,11 +21,6 @@ ;; 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 ;; gracefully for compatibility -(deftype NewContactKeyHandler [] - Object - (tag [this v] "c1") - (rep [this {:keys [sym-key topic message]}] - #js [sym-key topic message])) (deftype ContactRequestHandler [] Object @@ -102,8 +97,7 @@ (def writer (transit/writer :json {:handlers - {contact/NewContactKey (NewContactKeyHandler.) - contact/ContactRequest (ContactRequestHandler.) + {contact/ContactRequest (ContactRequestHandler.) contact/ContactRequestConfirmed (ContactRequestConfirmedHandler.) contact/ContactUpdate (ContactUpdateHandler.) protocol/Message (MessageHandler.) @@ -145,9 +139,7 @@ ;; Here we only need to call the record with the arguments parsed from the clojure datastructures (def reader (transit/reader :json {:handlers - {"c1" (fn [[sym-key topic message]] - (contact/NewContactKey. sym-key topic message)) - "c2" (fn [[name profile-image address fcm-token]] + {"c2" (fn [[name profile-image address fcm-token]] (contact/ContactRequest. name profile-image address fcm-token)) "c3" (fn [[name profile-image address fcm-token]] (contact/ContactRequestConfirmed. name profile-image address fcm-token)) diff --git a/src/status_im/ui/components/button/styles.cljs b/src/status_im/ui/components/button/styles.cljs index 794269c1dc..dac1419e33 100644 --- a/src/status_im/ui/components/button/styles.cljs +++ b/src/status_im/ui/components/button/styles.cljs @@ -42,6 +42,9 @@ {:font-weight :normal :color colors/white :padding-horizontal 16 + :desktop {:font-size 14 + :padding-vertical 10 + :letter-spacing 0.5} :android {:font-size 14 :padding-vertical 10 :letter-spacing 0.5} @@ -99,4 +102,4 @@ :flex 0.1 :padding-right 5 :align-items :center - :justify-content :center}) \ No newline at end of file + :justify-content :center}) diff --git a/src/status_im/ui/components/contact/contact.cljs b/src/status_im/ui/components/contact/contact.cljs index e9a29dd5b7..616e270e0f 100644 --- a/src/status_im/ui/components/contact/contact.cljs +++ b/src/status_im/ui/components/contact/contact.cljs @@ -58,7 +58,7 @@ [react/view styles/more-btn [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]] [react/view {:accessibility-label :contact-item} [list/list-item-with-checkbox diff --git a/src/status_im/ui/components/icons/vector_icons.cljs b/src/status_im/ui/components/icons/vector_icons.cljs index a4763ddc95..f5aa3bef2d 100644 --- a/src/status_im/ui/components/icons/vector_icons.cljs +++ b/src/status_im/ui/components/icons/vector_icons.cljs @@ -54,6 +54,7 @@ :icons/close (js/require "./resources/icons/close.svg") :icons/copy-from (js/require "./resources/icons/copy_from.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-vertical (js/require "./resources/icons/dots_vertical.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/lock (js/require "./resources/icons/lock.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/public (js/require "./resources/icons/public.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/copy-from (components.svg/slurp-svg "./resources/icons/copy_from.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-vertical (components.svg/slurp-svg "./resources/icons/dots_vertical.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-opened (components.svg/slurp-svg "./resources/icons/lock_opened.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/public (components.svg/slurp-svg "./resources/icons/public.svg") :icons/public-chat (components.svg/slurp-svg "./resources/icons/public_chat.svg") 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 35b7fa1863..bac10be7ab 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 @@ -80,13 +80,9 @@ [react/view [react/view {:style styles/title-separator}] [react/text {:style styles/mailserver-title} (i18n/label :devices)] - [react/touchable-highlight {:style styles/pair-button - :on-press pairing.views/pair!} - [react/text (i18n/label :pair)]] - (for [installation installations] - ^{:key (:installation-id installation)} - [react/view {:style {:margin-vertical 8}} - [pairing.views/render-row installation]])]) + [pairing.views/pair-this-device] + [pairing.views/sync-devices] + [pairing.views/installations-list installations]]) (views/defview advanced-settings [] (views/letsubs [installations [:pairing/installations] diff --git a/src/status_im/ui/screens/group/add_contacts/views.cljs b/src/status_im/ui/screens/group/add_contacts/views.cljs index db2273a178..cbcd0cb704 100644 --- a/src/status_im/ui/screens/group/add_contacts/views.cljs +++ b/src/status_im/ui/screens/group/add_contacts/views.cljs @@ -2,8 +2,11 @@ (:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require [re-frame.core :as re-frame] [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-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] @@ -18,10 +21,15 @@ (re-frame/dispatch [action public-key]))) (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] - [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] [toolbar/toolbar {} @@ -33,25 +41,39 @@ label])]) (defn toggle-list [contacts render-function] - [react/view {:flex 1} - [list/flat-list {:style styles/contacts-list - :data contacts - :key-fn :address - :render-fn render-function - :keyboardShouldPersistTaps :always}]]) + [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)]]) ;; Start group chat (defview contact-toggle-list [] (letsubs [contacts [:all-added-people-contacts] selected-contacts-count [:selected-contacts-count]] - (when (seq contacts) - [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)] - [toggle-list contacts group-toggle-contact]]))) + [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)] + (if (seq contacts) + [toggle-list contacts group-toggle-contact] + [no-contacts])])) ;; Add participants to existing group chat (defview add-participants-toggle-list [] diff --git a/src/status_im/ui/screens/group/styles.cljs b/src/status_im/ui/screens/group/styles.cljs index 1b2fba42ee..2ba14e2a3b 100644 --- a/src/status_im/ui/screens/group/styles.cljs +++ b/src/status_im/ui/screens/group/styles.cljs @@ -17,3 +17,9 @@ (def contacts-list {:background-color colors/white}) + +(def no-contact-text + {:margin-bottom 20 + :margin-horizontal 50 + :text-align :center + :color colors/gray}) diff --git a/src/status_im/ui/screens/pairing/styles.cljs b/src/status_im/ui/screens/pairing/styles.cljs index 94430a7a60..a697415aee 100644 --- a/src/status_im/ui/screens/pairing/styles.cljs +++ b/src/status_im/ui/screens/pairing/styles.cljs @@ -1,27 +1,78 @@ (ns status-im.ui.screens.pairing.styles (: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 {:flex 1 :background-color :white}) (def installation-item-inner - {:flex 1 - :flex-direction :row - :padding-horizontal 16}) + {:flex 1 + :flex-direction :row}) (defstyle installation-item - {:flex-direction :row - :background-color :white - :align-items :center - :padding-horizontal 16 - :ios {:height 64} - :android {:height 56}}) + {:flex-direction :row + :background-color :white + :align-items :center + :ios {:height 64} + :android {:height 56}}) (defstyle installation-item-name-text - {:color colors/black - :ios {:font-size 17 - :letter-spacing -0.2 - :line-height 20} - :android {:font-size 16}}) + {:color colors/black}) + +(def installation-list + {:background-color :white + :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}) diff --git a/src/status_im/ui/screens/pairing/subs.cljs b/src/status_im/ui/screens/pairing/subs.cljs index 3726a4c972..16f914614d 100644 --- a/src/status_im/ui/screens/pairing/subs.cljs +++ b/src/status_im/ui/screens/pairing/subs.cljs @@ -4,4 +4,7 @@ (re-frame/reg-sub :pairing/installations :<- [:get :pairing/installations] - vals) + (fn [k] + (->> k + vals + (filter :device-type)))) diff --git a/src/status_im/ui/screens/pairing/views.cljs b/src/status_im/ui/screens/pairing/views.cljs index fa5a362576..46260f8087 100644 --- a/src/status_im/ui/screens/pairing/views.cljs +++ b/src/status_im/ui/screens/pairing/views.cljs @@ -3,10 +3,12 @@ (:require [re-frame.core :as re-frame] [status-im.i18n :as i18n] [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.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.react :as react] [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.pairing.styles :as styles])) -(defn synchronize-installation! [id] - #_(re-frame/dispatch [:pairing.ui/synchronize-installation-pressed id])) +(defn icon-style [{:keys [width height] :as style}] + (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! [] (re-frame/dispatch [:pairing.ui/pair-devices-pressed])) @@ -27,39 +37,84 @@ (defn disable-installation! [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]}] [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} - [react/view styles/installation-item - [react/view styles/installation-item-inner + [react/view {:style styles/installation-item} + [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/text {:style styles/installation-item-name-text} - (str (gfycat/generate-gfy installation-id) " - " (or device-type - "unknown"))]] + (gfycat/generate-gfy installation-id)]] [react/view - (if enabled? - [buttons/primary-button - {:on-press (partial disable-installation! installation-id)} - (i18n/label :t/enabled)] - [buttons/secondary-button - {:on-press (partial enable-installation! installation-id)} - (i18n/label :t/disabled)])]]]]) + [react/text {:style styles/installation-status} + (if enabled? + (i18n/label :t/syncing-enabled) + (i18n/label :t/syncing-disabled))]]]]]) (defn render-rows [installations] - [react/view styles/wrapper + [react/view {:style styles/wrapper} [list/flat-list {:data installations :default-separator? false :key-fn :installation-id :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/letsubs [installations [:pairing/installations]] [react/view {:flex 1} [status-bar/status-bar] [toolbar/toolbar {} toolbar/default-nav-back - [toolbar/content-title (i18n/label :t/devices)] - [toolbar/actions - [(toolbar.actions/add false pair!)]]] - (render-rows installations)])) + [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])])) diff --git a/src/status_im/utils/fx.cljs b/src/status_im/utils/fx.cljs index 5d709a1f02..2e66f366ac 100644 --- a/src/status_im/utils/fx.cljs +++ b/src/status_im/utils/fx.cljs @@ -12,6 +12,7 @@ (def ^:private mergable-keys #{: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/send-direct-message :shh/generate-sym-key-from-password :transport/confirm-messages-processed :group-chats/extract-membership-signature :utils/dispatch-later}) diff --git a/test/cljs/status_im/test/pairing/core.cljs b/test/cljs/status_im/test/pairing/core.cljs index 0599d09296..d664d70377 100644 --- a/test/cljs/status_im/test/pairing/core.cljs +++ b/test/cljs/status_im/test/pairing/core.cljs @@ -1,6 +1,7 @@ (ns status-im.test.pairing.core (:require [cljs.test :refer-macros [deftest is testing]] [status-im.transport.message.pairing :as transport.pairing] + [status-im.utils.identicon :as identicon] [status-im.utils.config :as config] [status-im.pairing.core :as pairing])) @@ -72,40 +73,60 @@ (is (= expected (pairing/merge-contact contact-1 contact-2)))))) (deftest handle-sync-installation-test - (with-redefs [config/pairing-enabled? (constantly true)] - (let [old-contact-1 {:name "old-contact-one" - :last-updated 0 - :photo-path "old-contact-1" - :pending? true} - new-contact-1 {:name "new-contact-one" - :last-updated 1 - :photo-path "new-contact-1" - :pending? false} - old-contact-2 {:name "old-contact-2" - :last-updated 0 - :photo-path "old-contact-2" - :pending? false} - new-contact-2 {:name "new-contact-2" - :last-updated 1 - :photo-path "new-contact-2" - :pending? false} - contact-3 {:name "contact-3" - :photo-path "contact-3" - :pending? false} - contact-4 {:name "contact-4" - :photo-path "contact-4" - :pending? true} + (with-redefs [config/pairing-enabled? (constantly true) + identicon/identicon (constantly "generated")] + (let [old-contact-1 {:name "old-contact-one" + :public-key "contact-1" + :last-updated 0 + :photo-path "old-contact-1" + :pending? true} + new-contact-1 {:name "new-contact-one" + :public-key "contact-1" + :last-updated 1 + :photo-path "new-contact-1" + :pending? false} + old-contact-2 {:name "old-contact-2" + :public-key "contact-2" + :last-updated 0 + :photo-path "old-contact-2" + :pending? false} + new-contact-2 {:name "new-contact-2" + :public-key "contact-2" + :last-updated 1 + :photo-path "new-contact-2" + :pending? false} + 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"} :contacts/contacts {"contact-1" old-contact-1 "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 "contact-2" old-contact-2 - "contact-4" contact-4}} + "contact-4" contact-4 + "contact-5" remote-contact-5}} expected {"contact-1" new-contact-1 "contact-2" new-contact-2 "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" (is (not (pairing/handle-sync-installation cofx sync-message "not-us")))) (testing "coming from us" @@ -115,7 +136,7 @@ (deftest handle-pair-installation-test (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 :installation-id "1"} "2" {:has-bundle? false @@ -138,10 +159,26 @@ (deftest sync-installation-messages-test (testing "it creates a sync installation message" (let [cofx {:db {:account/account {:public-key "us"} - :contacts/contacts {"contact-1" {:name "contact-1"} - "contact-2" {:name "contact-2"}}}} - expected [(transport.pairing/SyncInstallation. {"contact-1" {:name "contact-1"}}) - (transport.pairing/SyncInstallation. {"contact-2" {:name "contact-2"}})]] + :contacts/contacts {"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"} + "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)))))) (deftest handle-bundles-added-test @@ -162,3 +199,11 @@ (testing "not from us" (let [new-installation {:identity "not-us" :installationID "does-not-matter"}] (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}}}})))) diff --git a/translations/en.json b/translations/en.json index f5c4cdcc4a..6e49d97bdd 100644 --- a/translations/en.json +++ b/translations/en.json @@ -6,8 +6,12 @@ "description": "Description", "devices": "Devices", "pair": "Pair devices", - "enabled": "enabled", - "disabled": "disabled", + "pair-this-device": "Pair this device", + "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-brl": "Brazil Real", "mainnet-network": "Main network", @@ -25,6 +29,7 @@ "group-chat-name-changed": "*{{member}}* changed the group's name to *{{name}}*", "group-chat-member-added": "*{{member}}* joined 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 ", "wallet-advanced": "Advanced", "currency-display-name-sos": "Somalia Shilling",