implemented fx handler, rewrite handlers using fx and cfx, optimized subscriptions, reorganized structure and renamed files, implemented re-frame-test and wrote some tests

This commit is contained in:
Andrey Shovkoplyas 2017-07-21 16:03:24 +03:00 committed by Roman Volosovskyi
parent 68fe57b016
commit 3d05f99bd4
55 changed files with 1512 additions and 1185 deletions

View File

@ -50,7 +50,8 @@
:optimizations :none}}}}
:repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]
:timeout 240000}}
:test {:plugins [[lein-doo "0.1.7"]]
:test {:dependencies [[day8.re-frame/test "0.1.5"]]
:plugins [[lein-doo "0.1.7"]]
:cljsbuild {:builds
[{:id "test"
:source-paths ["src" "test/cljs"]

View File

@ -15,9 +15,9 @@
http-bridge]]
[status-im.components.main-tabs :refer [main-tabs]]
[status-im.components.context-menu :refer [menu-context]]
[status-im.contacts.views.contact-list :refer [contact-list]]
[status-im.contacts.views.contact-list-modal :refer [contact-list-modal]]
[status-im.contacts.views.new-contact :refer [new-contact]]
[status-im.contacts.contact-list.views :refer [contact-list]]
[status-im.contacts.contact-list-modal.views :refer [contact-list-modal]]
[status-im.contacts.new-contact.views :refer [new-contact]]
[status-im.qr-scanner.screen :refer [qr-scanner]]
[status-im.discover.search-results :refer [discover-search-results]]
[status-im.chat.screen :refer [chat]]

View File

@ -266,8 +266,8 @@
db' (-> db
(assoc :current-chat-id chat-id)
(assoc-in [:chats chat-id :was-opened?] true))
commands-loaded? (get-in db [:contacts chat-id :commands-loaded?])
bot-url (get-in db [:contacts chat-id :bot-url])
commands-loaded? (get-in db [:contacts/contacts chat-id :commands-loaded?])
bot-url (get-in db [:contacts/contacts chat-id :bot-url])
was-opened? (get-in db [:chats chat-id :was-opened?])
call-init-command #(when (and (not was-opened?) bot-url)
(status/call-function!
@ -311,7 +311,7 @@
(callback))
(dispatch [::clear-chat-loaded-callbacks chat-id])))))
(defn prepare-chat [{:keys [contacts]} chat-id chat]
(defn prepare-chat [{:contacts/keys [contacts]} chat-id chat]
(let [name (get-in contacts [chat-id :name])]
(merge {:chat-id chat-id
:name (or name (generate-gfy))
@ -432,7 +432,8 @@
delete-chat!))
(defn send-seen!
[{:keys [web3 current-public-key chats contacts]}
[{:keys [web3 current-public-key chats]
:contacts/keys [contacts]}
[_ {:keys [from chat-id message-id]}]]
(when-not (get-in contacts [chat-id :dapp?])
(let [{:keys [group-chat public?]} (chats chat-id)]
@ -452,7 +453,8 @@
(u/side-effect! send-seen!))
(defn send-clock-value-request!
[{:keys [web3 current-public-key contacts]} [_ {:keys [message-id from]}]]
[{:keys [web3 current-public-key]
:contacts/keys [contacts]} [_ {:keys [message-id from]}]]
(when-not (get-in contacts [from :dapp?])
(protocol/send-clock-value-request!
{:web3 web3
@ -487,8 +489,9 @@
(register-handler :check-and-open-dapp!
(u/side-effect!
(fn [{:keys [current-chat-id global-commands contacts] :as db}]
(let [dapp-url (get-in db [:contacts current-chat-id :dapp-url])]
(fn [{:keys [current-chat-id global-commands]
:contacts/keys [contacts]}]
(let [dapp-url (get-in contacts [current-chat-id :dapp-url])]
(when dapp-url
(am/go
(dispatch [:select-chat-input-command

View File

@ -11,7 +11,7 @@
[status-im.utils.platform :as platform]
[taoensso.timbre :as log]))
(defn generate-context [{:keys [contacts current-account-id chats] :as db} chat-id to]
(defn generate-context [{:keys [current-account-id chats] :as db} chat-id to]
(merge {:platform platform/platform
:from current-account-id
:to to
@ -21,7 +21,8 @@
(handlers/register-handler :request-command-data
(handlers/side-effect!
(fn [{:keys [contacts current-account-id chats] :as db}
(fn [{:keys [current-account-id chats]
:contacts/keys [contacts] :as db}
[_ {{:keys [command params content-command type]} :content
:keys [message-id chat-id jail-id on-requested from] :as message} data-type]]
(let [jail-id (or jail-id chat-id)

View File

@ -117,7 +117,7 @@
all-commands (->> (into global-commands commands)
(remove (fn [[k {:keys [hidden?]}]] hidden?))
(into {}))
{:keys [dapp?]} (get-in db [:contacts chat-id])]
{:keys [dapp?]} (get-in db [:contacts/contacts chat-id])]
(when dapp?
(if (str/blank? chat-text)
(dispatch [:set-in [:chats chat-id :parameter-boxes :message] nil])
@ -144,7 +144,7 @@
(input-model/split-command-args)
(rest))
seq-arg (get-in db [:chats current-chat-id :seq-argument-input-text])
to (get-in db [:contacts current-chat-id :address])
to (get-in db [:contacts/contacts current-chat-id :address])
params {:parameters {:args args
:bot-db bot-db
:seq-arg seq-arg}
@ -237,7 +237,8 @@
(handlers/register-handler ::request-command-data
(handlers/side-effect!
(fn [{:keys [contacts bot-db] :as db}
(fn [{:keys [bot-db]
:contacts/keys [contacts] :as db}
[_ {{:keys [command
metadata
args]

View File

@ -97,7 +97,7 @@
(assoc-in db [:chats chat-id :last-message] message)))
(defn commands-loaded? [db chat-id]
(get-in db [:contacts chat-id :commands-loaded?]))
(get-in db [:contacts/contacts chat-id :commands-loaded?]))
(def timeout 400)

View File

@ -137,7 +137,8 @@
(register-handler ::invoke-command-handlers!
(u/side-effect!
(fn [{:keys [bot-db accounts current-account-id] :as db}
(fn [{:keys [bot-db accounts current-account-id]
:contacts/keys [contacts] :as db}
[_ {{:keys [command
params
id]} :command
@ -145,7 +146,7 @@
:as orig-params}]]
(let [{:keys [type name bot owner-id]} command
handler-type (if (= :command type) :commands :responses)
to (get-in db [:contacts chat-id :address])
to (get-in contacts [chat-id :address])
identity (or owner-id bot chat-id)
bot-db (get bot-db (or bot chat-id))
params {:parameters params
@ -217,7 +218,7 @@
(register-handler :received-bot-response
(u/side-effect!
(fn [{:keys [contacts]} [_ {:keys [chat-id] :as params} {:keys [result] :as data}]]
(fn [{:contacts/keys [contacts]} [_ {:keys [chat-id] :as params} {:keys [result] :as data}]]
(let [{:keys [returned context]} result
{:keys [markup text-message err]} returned
{:keys [log-messages update-db default-db]} context
@ -287,10 +288,11 @@
(register-handler ::send-message!
(u/side-effect!
(fn [{:keys [web3 chats network-status current-account-id accounts]
:contacts/keys [contacts]
:as db} [_ {{:keys [message-type]
:as message} :message
chat-id :chat-id}]]
(let [{:keys [dapp?]} (get-in db [:contacts chat-id])]
(let [{:keys [dapp?]} (get contacts chat-id)]
(if dapp?
(dispatch [::send-dapp-message chat-id message])
(when message
@ -323,7 +325,8 @@
(register-handler ::send-command-protocol!
(u/side-effect!
(fn [{:keys [web3 current-public-key chats network-status
current-account-id accounts contacts] :as db}
current-account-id accounts]
:contacts/keys [contacts] :as db}
[_ {:keys [chat-id command]}]]
(log/debug "sending command: " command)
(if (get-in contacts [chat-id :dapp?])

View File

@ -7,7 +7,7 @@
[taoensso.timbre :as log]
[status-im.models.commands :as commands]
[status-im.commands.utils :as cu]
[status-im.contacts.validations :as v]
[status-im.contacts.db :as v]
[status-im.components.status :as s]
[status-im.components.nfc :as nfc]
[status-im.constants :as c]
@ -24,7 +24,7 @@
(str "\"" s "\"")))
(defn scan-qr-handler
[{:keys [contacts]} [_ _ data]]
[{:contacts/keys [contacts]} [_ _ data]]
(let [data' (try (read-string (wrap-hex data))
(catch :default e data))
data'' (cond
@ -60,9 +60,10 @@
(register-handler ::send-command
(u/side-effect!
(fn [{:keys [current-chat-id] :as db}
(fn [{:keys [current-chat-id]
:contacts/keys [contacts]}
[_ command-key {:keys [contact amount]}]]
(let [command (get-in db [:contacts current-chat-id :commands command-key])]
(let [command (get-in contacts [current-chat-id :commands command-key])]
(dispatch [:set-in [:bot-db current-chat-id :public :recipient] contact])
(dispatch [:proceed-command
{:command command,
@ -107,9 +108,9 @@
(defmethod nav/preload-data! :contact-list-modal
[db [_ _ {:keys [handler action params]}]]
(assoc db :contacts-click-handler handler
:contacts-click-action action
:contacts-click-params params))
(assoc db :contacts/click-handler handler
:contacts/click-action action
:contacts/click-params params))
(def qr-context {:toolbar-title (label :t/address)})

View File

@ -50,7 +50,7 @@
{:keys [contacts requests]} (get-in db [:chats chat-id])]
(->> contacts
(map (fn [{:keys [identity]}]
(let [{:keys [commands responses]} (get-in db [:contacts identity])]
(let [{:keys [commands responses]} (get-in db [:contacts/contacts identity])]
(let [commands' (mapv (fn [[k v]] [k [v :any]]) (merge global-commands commands))
responses' (mapv (fn [{:keys [message-id type]}]
[type [(get responses type) message-id]])

View File

@ -23,14 +23,14 @@
(let [requests (get-in db [:chats current-chat-id :requests])]
(->> requests
(map (fn [{:keys [type] :as v}]
(assoc v :name (get-in db [:contacts current-chat-id :responses type :name]))))
(assoc v :name (get-in db [:contacts/contacts current-chat-id :responses type :name]))))
(filter (fn [v] ((can-be-suggested? text) v))))))
(defn get-command-suggestions
[{:keys [current-chat-id] :as db} text]
(->> (get-in db [:chats current-chat-id :contacts])
(map (fn [{:keys [identity]}]
(let [commands (get-in db [:contacts identity :commands])]
(let [commands (get-in db [:contacts/contacts identity :commands])]
(->> commands
(filter (fn [[_ v]] ((can-be-suggested? text) v)))))))
(reduce (fn [m cur] (into (or m {}) cur)))

View File

@ -27,7 +27,7 @@
(reg-sub
:chats
(fn [db ]
(fn [db]
(:chats db)))
(reg-sub
@ -39,7 +39,7 @@
(reg-sub
:get-current-chat-id
(fn [db ]
(fn [db]
(:current-chat-id db)))
(reg-sub
@ -55,23 +55,22 @@
(reg-sub :get-commands
(fn [db [_ chat-id]]
(let [current-chat (or chat-id (db :current-chat-id))]
(or (get-in db [:contacts current-chat :commands]) {}))))
(or (get-in db [:contacts/contacts current-chat :commands]) {}))))
(reg-sub
:get-responses
(fn [db [_ chat-id]]
(let [current-chat (or chat-id (db :current-chat-id))]
(or (get-in db [:contacts current-chat :responses]) {}))))
(or (get-in db [:contacts/contacts current-chat :responses]) {}))))
(reg-sub :get-commands-and-responses
(fn [db [_ chat-id]]
(let [{:keys [chats contacts]} db]
(->> (get-in chats [chat-id :contacts])
(filter :is-in-chat)
(mapv (fn [{:keys [identity]}]
(let [{:keys [commands responses]} (get contacts identity)]
(merge responses commands))))
(apply merge)))))
(fn [{:keys [chats] :contacts/keys [contacts]} [_ chat-id]]
(->> (get-in chats [chat-id :contacts])
(filter :is-in-chat)
(mapv (fn [{:keys [identity]}]
(let [{:keys [commands responses]} (get contacts identity)]
(merge responses commands))))
(apply merge))))
(reg-sub
:selected-chat-command
@ -148,7 +147,7 @@
(reg-sub :get-response
(fn [db [_ n]]
(let [chat-id (subscribe [:get-current-chat-id])]
(get-in db [:contacts @chat-id :responses n]))))
(get-in db [:contacts/contacts @chat-id :responses n]))))
(reg-sub :is-request-answered?
:<- [:chat :requests]
@ -170,7 +169,7 @@
(get-in db [:chats @chat-id :all-loaded?]))))
(reg-sub :photo-path
:<- [:get :contacts]
:<- [:get-contacts]
(fn [contacts [_ id]]
(:photo-path (contacts id))))
@ -178,9 +177,9 @@
(fn [db [_ chat-id]]
(let [{:keys [last-message messages]} (get-in db [:chats chat-id])]
(->> (conj messages last-message)
(sort-by :clock-value > )
(filter :show?)
(first)))))
(sort-by :clock-value >)
(filter :show?)
(first)))))
(reg-sub :get-last-message-short-preview
(fn [db [_ chat-id]]

View File

@ -69,7 +69,7 @@
public? [:chat :public?]
show-actions? [:chat-ui-props :show-actions?]
accounts [:get :accounts]
contact [:get-in [:contacts @chat-id]]
contact [:get-in [:contacts/contacts @chat-id]]
sync-state [:get :sync-state]
creating? [:get :creating-account?]]
[view (st/chat-name-view (or (empty? accounts)

View File

@ -37,7 +37,8 @@
:else nil)))
(defn suggestions-handler!
[{:keys [contacts chats] :as db} [{:keys [chat-id default-db command parameter-index result]}]]
[{:keys [chats] :as db}
[{:keys [chat-id default-db command parameter-index result]}]]
(let [{:keys [markup height] :as returned} (get-in result [:result :returned])
contains-markup? (contains? returned :markup)
path (if command
@ -53,7 +54,7 @@
(defn suggestions-events-handler!
[{:keys [current-chat-id bot-db] :as db} [[n & data :as ev] val]]
(log/debug "Suggestion event: " n (first data) val)
(let [{:keys [dapp?]} (get-in db [:contacts current-chat-id])]
(let [{:keys [dapp?]} (get-in db [:contacts/contacts current-chat-id])]
(case (keyword n)
:set-command-argument
(let [[index value move-to-next?] (first data)]

View File

@ -22,7 +22,8 @@
(defn load-commands!
[{:keys [current-chat-id contacts chats]} [jail-id callback]]
[{:keys [current-chat-id chats]
:contacts/keys [contacts]} [jail-id callback]]
(let [identity (or jail-id current-chat-id)
contact-ids (if (get contacts identity)
[identity]
@ -67,7 +68,7 @@
(defn get-hash-by-identity
[db identity]
(get-in db [:contacts identity :dapp-hash]))
(get-in db [:contacts/contacts identity :dapp-hash]))
(defn get-hash-by-file
[file]
@ -121,7 +122,7 @@
(into {})))
(defn get-mailmans-commands [db]
(->> (get-in db [:contacts bots-constants/mailman-bot :commands])
(->> (get-in db [:contacts/contacts bots-constants/mailman-bot :commands])
(map
(fn [[k v :as com]]
[k (-> v
@ -147,7 +148,7 @@
(cond-> db
true
(update-in [:contacts id] assoc
(update-in [:contacts/contacts id] assoc
:commands-loaded? true
:commands (merge mailman-commands commands'')
:responses (each-merge responses' {:type :response
@ -160,20 +161,20 @@
:type :command))
(= id bots-constants/mailman-bot)
(update :contacts (fn [contacts]
(reduce (fn [contacts [k _]]
(update-in contacts [k :commands]
(fn [c]
(merge mailman-commands c))))
contacts
contacts))))))
(update :contacts/contacts (fn [contacts]
(reduce (fn [contacts [k _]]
(update-in contacts [k :commands]
(fn [c]
(merge mailman-commands c))))
contacts
contacts))))))
(defn save-commands-js!
[_ [id file]]
#_(commands/save {:chat-id id :file file}))
(defn save-commands!
[{:keys [global-commands contacts]} [id]]
[{:keys [global-commands] :contacts/keys [contacts]} [id]]
(let [command (get global-commands (keyword id))
commands (get-in contacts [id :commands])
responses (get-in contacts [id :responses])]
@ -195,7 +196,7 @@
(reg-handler :check-and-load-commands!
(u/side-effect!
(fn [{:keys [contacts]} [identity callback]]
(fn [{:contacts/keys [contacts]} [identity callback]]
(if (get-in contacts [identity :commands-loaded?])
(callback)
(dispatch [:load-commands! identity callback])))))
@ -216,7 +217,7 @@
(after (fn [_ [id]]
(dispatch [:invoke-commands-loading-callbacks id])
(dispatch [:invoke-chat-loaded-callbacks id])))
(after (fn [{:keys [contacts]} [id]]
(after (fn [{:contacts/keys [contacts]} [id]]
(let [subscriptions (get-in contacts [id :subscriptions])]
(doseq [[name opts] subscriptions]
(dispatch [:register-bot-subscription

View File

@ -34,7 +34,7 @@
(defview pending-contact-badge
[chat-id {:keys [pending-wrapper pending-outer-circle pending-inner-circle]}]
[pending-contact? [:get-in [:contacts chat-id :pending?]]]
[pending-contact? [:get-in [:contacts/contacts chat-id :pending?]]]
(when pending-contact?
[view pending-wrapper
[view pending-outer-circle
@ -42,7 +42,7 @@
(defview chat-icon-view [chat-id group-chat name online styles & [hide-dapp?]]
[photo-path [:chat-photo chat-id]
dapp? [:get-in [:contacts chat-id :dapp?]]]
dapp? [:get-in [:contacts/contacts chat-id :dapp?]]]
[view (:container styles)
(if-not (s/blank? photo-path)
[chat-icon photo-path styles]

View File

@ -9,7 +9,7 @@
[status-im.components.tabs.bottom-shadow :refer [bottom-shadow-view]]
[status-im.chats-list.screen :refer [chats-list]]
[status-im.discover.screen :refer [discover]]
[status-im.contacts.screen :refer [contact-list]]
[status-im.contacts.views :refer [contact-list]]
[status-im.components.tabs.tabs :refer [tabs]]
[status-im.components.tabs.styles :as st]
[status-im.components.styles :as common-st]

View File

@ -0,0 +1,69 @@
(ns status-im.contacts.contact-list.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.contacts.views :refer [contact-options]]
[status-im.components.react :refer [view list-view list-item]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar-with-search toolbar]]
[status-im.components.toolbar-new.actions :as act]
[status-im.components.drawer.view :refer [drawer-view]]
[status-im.contacts.styles :as st]
[status-im.utils.listview :as lw]
[status-im.i18n :refer [label]]))
(defn render-row [group edit?]
(fn [row _ _]
(list-item
^{:key row}
[contact-view {:contact row
:on-press #(dispatch [:open-chat-with-contact %])
:extended? edit?
:extend-options (contact-options row group)}])))
(defview contact-list-toolbar-edit [group]
[toolbar {:nav-action (act/back #(dispatch [:set-in [:contacts/list-ui-props :edit?] false]))
:actions [{:image :blank}]
:title (if-not group
(label :t/contacts)
(or (:name group) (label :t/contacts-group-new-chat)))}])
(defview contact-list-toolbar [group]
(letsubs [show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]]
(toolbar-with-search
{:show-search? (= show-search :contact-list)
:search-text search-text
:search-key :contact-list
:title (if-not group
(label :t/contacts)
(or (:name group) (label :t/contacts-group-new-chat)))
:search-placeholder (label :t/search-contacts)
:actions [(act/opts [{:text (label :t/edit)
:value #(dispatch [:set-in [:contacts/list-ui-props :edit?] true])}])]})))
(defview contacts-list-view [group edit?]
(letsubs [contacts [:all-added-group-contacts-filtered (:group-id group)]]
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow (render-row group edit?)
:keyboardShouldPersistTaps :always
:renderHeader renderers/list-header-renderer
:renderFooter renderers/list-footer-renderer
:renderSeparator renderers/list-separator-renderer
:style st/contacts-list}]))
(defview contact-list []
(letsubs [edit? [:get-in [:contacts/list-ui-props :edit?]]
group [:get :contacts-group]
type [:get :group-type]]
[drawer-view
[view {:flex 1}
[view
[status-bar]
(if edit?
[contact-list-toolbar-edit group]
[contact-list-toolbar group])]
[contacts-list-view group edit?]]]))

View File

@ -0,0 +1,78 @@
(ns status-im.contacts.contact-list-modal.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.common.common :as common]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.react :refer [view list-view list-item]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.components.action-button.action-button :refer [action-button
action-separator]]
[status-im.components.action-button.styles :refer [actions-list]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar-with-search]]
[status-im.components.drawer.view :refer [drawer-view]]
[status-im.contacts.styles :as st]
[status-im.utils.listview :as lw]
[status-im.i18n :refer [label]]))
(defview contact-list-modal-toolbar []
(letsubs [show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]]
(toolbar-with-search
{:show-search? (= show-search :contact-list)
:search-text search-text
:search-key :contact-list
:title (label :t/contacts)
:search-placeholder (label :t/search-contacts)})))
(defn actions-view [action click-handler]
[view actions-list
[action-button (label :t/enter-address)
:address_blue
#(do
(dispatch [:send-to-webview-bridge
{:event (name :webview-send-transaction)}])
(dispatch [:navigate-back]))]
[action-separator]
(if (= :request action)
[action-button (label :t/show-qr)
:q_r_blue
#(click-handler :qr-scan action)]
[action-button (label :t/scan-qr)
:fullscreen_blue
#(click-handler :qr-scan action)])])
(defn render-row [click-handler action params]
(fn [row _ _]
(list-item
^{:key row}
[contact-view {:contact row
:on-press #(when click-handler
(click-handler row action params))}])))
(defview contact-list-modal []
(letsubs [contacts [:contacts-filtered :all-added-people-contacts]
click-handler [:get :contacts/click-handler]
action [:get :contacts/click-action]
params [:get :contacts/click-params]]
[drawer-view
[view {:flex 1}
[status-bar {:type :modal}]
[contact-list-modal-toolbar]
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow (render-row click-handler action params)
:bounces false
:keyboardShouldPersistTaps :always
:renderHeader #(list-item
[view
[actions-view action click-handler]
[common/bottom-shadow]
[common/form-title (label :t/choose-from-contacts)
{:count-value (count contacts)}]
[common/list-header]])
:renderFooter #(list-item [view
[common/list-footer]
[common/bottom-shadow]])
:renderSeparator renderers/list-separator-renderer
:style st/contacts-list-modal}]]]))

View File

@ -0,0 +1,96 @@
(ns status-im.contacts.db
(:require-macros [status-im.utils.db :refer [allowed-keys]])
(:require [cljs.spec.alpha :as s]
[clojure.string :as str]
[status-im.data-store.contacts :as contacts]
[status-im.js-dependencies :as dependencies]))
(defn contact-can-be-added? [identity]
(if (contacts/exists? identity)
(:pending? (contacts/get-by-id identity))
true))
(defn is-address? [s]
(.isAddress dependencies/Web3.prototype s))
(defn hex-string? [s]
(let [s' (if (str/starts-with? s "0x")
(subs s 2)
s)]
(boolean (re-matches #"(?i)[0-9a-f]+" s'))))
(defn valid-length? [identity]
(let [length (count identity)]
(and
(hex-string? identity)
(or
(and (= 128 length) (not (str/includes? identity "0x")))
(and (= 130 length) (str/starts-with? identity "0x"))
(and (= 132 length) (str/starts-with? identity "0x04"))
(is-address? identity)))))
(s/def ::not-empty-string (s/and string? not-empty))
(s/def ::public-key (s/and ::not-empty-string valid-length?))
;;;; DB
;;Contact
;we can't validate public key, because for dapps whisper-identity is just string
(s/def :contact/whisper-identity ::not-empty-string)
(s/def :contact/name ::not-empty-string)
(s/def :contact/address (s/nilable is-address?))
(s/def :contact/private-key (s/nilable string?))
(s/def :contact/public-key (s/nilable string?))
(s/def :contact/photo-path (s/nilable string?))
(s/def :contact/status (s/nilable string?))
(s/def :contact/last-updated (s/nilable int?))
(s/def :contact/last-online (s/nilable int?))
(s/def :contact/pending? boolean?)
(s/def :contact/unremovable? boolean?)
(s/def :contact/dapp? boolean?)
(s/def :contact/dapp-url (s/nilable string?))
(s/def :contact/dapp-hash (s/nilable int?))
(s/def :contact/bot-url (s/nilable string?))
(s/def :contact/global-command (s/nilable map?))
(s/def :contact/commands (s/nilable (s/map-of keyword? map?)))
(s/def :contact/responses (s/nilable (s/map-of keyword? map?)))
(s/def :contact/commands-loaded? (s/nilable boolean?))
(s/def :contact/subscriptions (s/nilable map?))
;true when contact added using status-dev-cli
(s/def :contact/debug? boolean?)
(s/def :contact/contact (allowed-keys
:req-un [:contact/name :contact/whisper-identity]
:opt-un [:contact/address :contact/private-key :contact/public-key :contact/photo-path
:contact/status :contact/last-updated :contact/last-online :contact/pending?
:contact/unremovable? :contact/dapp? :contact/dapp-url :contact/dapp-hash
:contact/bot-url :contact/global-command :contact/commands-loaded?
:contact/commands :contact/responses :contact/debug? :contact/subscriptions]))
;;Contact list ui props
(s/def :contact-list-ui/edit? boolean?)
;;Contacts ui props
(s/def :contacts-ui/edit? boolean?)
(s/def :contacts/contacts (s/nilable (s/map-of ::not-empty-string :contact/contact)))
;public key of new contact during adding this new contact
(s/def :contacts/new-identity (s/nilable string?))
(s/def :contacts/new-public-key-error (s/nilable string?))
;on showing this contact's profile (andrey: better to move into profile ns)
(s/def :contacts/identity (s/nilable ::not-empty-string))
(s/def :contacts/list-ui-props (s/nilable (allowed-keys :opt-un [:contact-list-ui/edit?])))
(s/def :contacts/ui-props (s/nilable (allowed-keys :opt-un [:contacts-ui/edit?])))
;used in modal list (for example for wallet)
(s/def :contacts/click-handler (s/nilable fn?))
;used in modal list (for example for wallet)
(s/def :contacts/click-action (s/nilable #{:send :request}))
;used in modal list (for example for wallet)
(s/def :contacts/click-params (s/nilable map?))

View File

@ -0,0 +1,456 @@
(ns status-im.contacts.events
(:require [re-frame.core :refer [dispatch reg-fx reg-cofx inject-cofx]]
[status-im.utils.handlers :refer [register-handler-db register-handler-fx]]
[status-im.data-store.contacts :as contacts]
[status-im.utils.crypt :refer [encrypt]]
[clojure.string :as s]
[status-im.protocol.core :as protocol]
[status-im.utils.utils :refer [http-post]]
[status-im.utils.phone-number :refer [format-phone-number]]
[status-im.utils.random :as random]
[taoensso.timbre :as log]
[cljs.reader :refer [read-string]]
[status-im.utils.js-resources :as js-res]
[status-im.react-native.js-dependencies :as rn-dependencies]
[status-im.contacts.navigation]
[status-im.utils.identicon :refer [identicon]]
[status-im.utils.gfycat.core :refer [generate-gfy]]
[status-im.i18n :refer [label]]
[status-im.contacts.db :as v]))
;;;; COFX
(reg-cofx
::get-all-contacts
(fn [coeffects _]
(assoc coeffects :all-contacts (contacts/get-all))))
(reg-cofx
::get-default-contacts-and-groups
(fn [coeffects _]
(assoc coeffects :default-contacts js-res/default-contacts
:default-groups js-res/default-contact-groups)))
;;;; FX
(reg-fx
::watch-contact
(fn [{:keys [web3 whisper-identity public-key private-key]}]
(protocol/watch-user! {:web3 web3
:identity whisper-identity
:keypair {:public public-key
:private private-key}
:callback #(dispatch [:incoming-message %1 %2])})))
(reg-fx
::stop-watching-contact
(fn [{:keys [web3 whisper-identity]}]
(protocol/stop-watching-user! {:web3 web3
:identity whisper-identity})))
(reg-fx
::send-contact-request-fx
(fn [{:keys [web3 current-public-key name whisper-identity photo-path current-account-id status
updates-public-key updates-private-key] :as params}]
(protocol/contact-request!
{:web3 web3
:message {:from current-public-key
:to whisper-identity
:message-id (random/id)
:payload {:contact {:name name
:profile-image photo-path
:address current-account-id
:status status}
:keypair {:public updates-public-key
:private updates-private-key}}}})))
(reg-fx
::reset-pending-messages
(fn [from]
(protocol/reset-pending-messages! from)))
(reg-fx
::save-contact
(fn [contact]
(contacts/save contact)))
(reg-fx
::save-contacts!
(fn [new-contacts]
(contacts/save-all new-contacts)))
(reg-fx
::delete-contact
(fn [contact]
(contacts/delete contact)))
(defn- contact-name [contact]
(->> contact
((juxt :givenName :middleName :familyName))
(remove s/blank?)
(s/join " ")))
(defn- normalize-phone-contacts [contacts]
(let [contacts' (js->clj contacts :keywordize-keys true)]
(map (fn [{:keys [thumbnailPath phoneNumbers] :as contact}]
{:name (contact-name contact)
:photo-path thumbnailPath
:phone-numbers phoneNumbers}) contacts')))
(reg-fx
::fetch-contacts-from-phone!
(fn [_]
(.getAll rn-dependencies/contacts
(fn [error contacts]
(if error
(log/debug :error-on-fetching-loading error)
(let [contacts' (normalize-phone-contacts contacts)]
(dispatch [::get-contacts-identities contacts'])))))))
(defn- get-contacts-by-hash [contacts]
(->> contacts
(mapcat (fn [{:keys [phone-numbers] :as contact}]
(map (fn [{:keys [number]}]
(let [number' (format-phone-number number)]
[(encrypt number')
(-> contact
(assoc :phone-number number')
(dissoc :phone-numbers))]))
phone-numbers)))
(into {})))
(defn- add-identity [contacts-by-hash contacts]
(map (fn [{:keys [phone-number-hash whisper-identity address]}]
(let [contact (contacts-by-hash phone-number-hash)]
(assoc contact :whisper-identity whisper-identity
:address address)))
(js->clj contacts)))
(reg-fx
::request-stored-contacts
(fn [contacts]
(let [contacts-by-hash (get-contacts-by-hash contacts)
data (or (keys contacts-by-hash) '())]
(http-post "get-contacts" {:phone-number-hashes data}
(fn [{:keys [contacts]}]
(let [contacts' (add-identity contacts-by-hash contacts)]
(dispatch [:add-contacts contacts'])))))))
(reg-fx
::request-contacts-by-address
(fn [id]
(http-post "get-contacts-by-address" {:addresses [id]}
(fn [{:keys [contacts]}]
(if (> (count contacts) 0)
(let [{:keys [whisper-identity]} (first contacts)
contact {:name (generate-gfy)
:address id
:photo-path (identicon whisper-identity)
:whisper-identity whisper-identity}]
(if (contacts/exists? whisper-identity)
(dispatch [:add-pending-contact whisper-identity])
(dispatch [:add-new-contact-and-open-chat contact])))
(dispatch [:set :contacts/new-public-key-error (label :t/unknown-address)]))))))
;;;; Handlers
(register-handler-fx
::get-contacts-identities
(fn [_ [_ contacts]]
{::request-stored-contacts contacts}))
(register-handler-fx
:watch-contact
(fn [{:keys [db]} [_ {:keys [public-key private-key] :as contact}]]
(when (and public-key private-key)
{::watch-contact (merge
(select-keys db [:web3])
(select-keys contact [:whisper-identity :public-key :private-key]))})))
(register-handler-fx
:update-contact!
(fn [{:keys [db]} [_ {:keys [whisper-identity] :as contact}]]
(when (get-in db [:contacts/contacts whisper-identity])
{:db (update-in db [:contacts/contacts whisper-identity] merge contact)
::save-contact contact})))
(register-handler-fx
:load-contacts
[(inject-cofx ::get-all-contacts)]
(fn [{:keys [db all-contacts]} _]
(let [contacts-list (map #(vector (:whisper-identity %) %) all-contacts)
global-commands (->> contacts-list
(filter (fn [[_ c]] (:global-command c)))
(map (fn [[id {:keys [global-command]}]]
[(keyword id) (-> global-command
(update :params (comp vec vals))
(assoc :bot id
:type :command))]))
(into {}))
contacts (into {} contacts-list)]
{:db (assoc db :contacts/contacts contacts
:global-commands global-commands)
:dispatch-n (mapv (fn [_ contact] [:watch-contact contact]) contacts)})))
(register-handler-fx
:sync-contacts
(fn [_ _]
{::fetch-contacts-from-phone! nil}))
(defn- public-key->address [public-key]
(let [length (count public-key)
normalized-key (case length
132 (subs public-key 4)
130 (subs public-key 2)
128 public-key
nil)]
(when normalized-key
(subs (.sha3 js/Web3.prototype normalized-key #js {:encoding "hex"}) 26))))
(defn- prepare-default-groups-events [groups default-groups]
[[:add-groups
(for [[id {:keys [name contacts]}] default-groups
:let [id' (clojure.core/name id)]
:when (not (get groups id'))]
{:group-id id'
:name (:en name)
:order 0
:timestamp (random/timestamp)
:contacts (mapv #(hash-map :identity %) contacts)})]])
(defn- prepare-default-contacts-events [contacts default-contacts]
[[:add-contacts
(for [[id {:keys [name photo-path public-key add-chat? global-command
dapp? dapp-url dapp-hash bot-url unremovable?]}] default-contacts
:let [id' (clojure.core/name id)]
:when (not (get contacts id'))]
{:whisper-identity id'
:address (public-key->address id')
:name (:en name)
:photo-path photo-path
:public-key public-key
:unremovable? (boolean unremovable?)
:dapp? dapp?
:dapp-url (:en dapp-url)
:bot-url bot-url
:global-command global-command
:dapp-hash dapp-hash})]])
(defn- prepare-add-chat-events [contacts default-contacts]
(for [[id {:keys [name add-chat?]}] default-contacts
:let [id' (clojure.core/name id)]
:when (and (not (get contacts id')) add-chat?)]
[:add-chat id' {:name (:en name)}]))
(defn- prepare-bot-commands-events [contacts default-contacts]
(for [[id {:keys [bot-url]}] default-contacts
:let [id' (clojure.core/name id)]
:when bot-url]
[:load-commands! id']))
(defn- prepare-add-contacts-to-groups-events [contacts default-contacts]
(let [groups (for [[id {:keys [groups]}] default-contacts
:let [id' (clojure.core/name id)]
:when (and (not (get contacts id')) groups)]
(for [group groups]
{:group-id group :whisper-identity id'}))
groups' (vals (group-by :group-id (flatten groups)))]
(for [contacts groups']
[:add-contacts-to-group
(:group-id (first contacts))
(mapv :whisper-identity contacts)])))
(register-handler-fx
:load-default-contacts!
[(inject-cofx ::get-default-contacts-and-groups)]
(fn [{:keys [db default-contacts default-groups]} _]
(let [{:keys [groups] :contacts/keys [contacts]} db]
{:dispatch-n (concat
(prepare-default-groups-events groups default-groups)
(prepare-default-contacts-events contacts default-contacts)
(prepare-add-chat-events contacts default-contacts)
(prepare-bot-commands-events contacts default-contacts)
(prepare-add-contacts-to-groups-events contacts default-contacts))})))
(register-handler-db
:remove-contacts-click-handler
(fn [db _]
(dissoc db
:contacts/click-handler
:contacts/click-action)))
(defn- update-pending-status [old-contacts {:keys [whisper-identity pending?] :as contact}]
(let [{old-pending :pending?
:as old-contact} (get old-contacts whisper-identity)
pending?' (if old-contact (and old-pending pending?) pending?)]
(assoc contact :pending? (boolean pending?'))))
(register-handler-fx
:add-contacts
(fn [{:keys [db]} [_ new-contacts]]
(let [{:contacts/keys [contacts]} db
identities (set (keys contacts))
new-contacts' (->> new-contacts
(map #(update-pending-status contacts %))
(remove #(identities (:whisper-identity %)))
(map #(vector (:whisper-identity %) %))
(into {}))
global-commands (->> new-contacts'
(keep (fn [[n {:keys [global-command]}]]
(when global-command
[(keyword n) (assoc global-command
:type :command
:bot n)])))
(into {}))
new-contacts-vals (vals new-contacts')]
{:db (-> db
(update :global-commands merge global-commands)
(update :contacts/contacts merge new-contacts'))
::save-contacts! new-contacts-vals})))
(register-handler-fx
::send-contact-request
(fn [{{:keys [accounts current-account-id] :as db} :db} [_ contact]]
(let [current-account (get accounts current-account-id)]
{::send-contact-request-fx (merge
(select-keys db [:current-public-key :web3 :current-account-id :accounts])
(select-keys contact [:whisper-identity])
(select-keys current-account [:name :photo-path :status
:updates-public-key :updates-private-key]))})))
(register-handler-fx
::add-new-contact
(fn [{:keys [db]} [_ {:keys [whisper-identity] :as contact}]]
{:db (-> db
(update-in [:contacts/contacts whisper-identity] merge contact)
(assoc :contacts/new-identity ""))
:dispatch [::send-contact-request contact]
::save-contact contact}))
(register-handler-fx
:add-new-contact-and-open-chat
(fn [{:keys [db]} [_ {:keys [whisper-identity] :as contact}]]
(when-not (get-in db [:contacts/contacts whisper-identity])
(let [contact (assoc contact :address (public-key->address whisper-identity))]
{:dispatch-n [[::add-new-contact contact]
[:start-chat whisper-identity {} :navigation-replace]]}))))
(register-handler-fx
:add-pending-contact
(fn [{:keys [db]} [_ chat-id]]
(let [{:keys [chats] :contacts/keys [contacts]} db
contact (if-let [contact-info (get-in chats [chat-id :contact-info])]
(read-string contact-info)
(get contacts chat-id))
contact' (assoc contact :address (public-key->address chat-id)
:pending? false)]
{:dispatch-n [[::add-new-contact contact']
[:watch-contact contact']
[:discoveries-send-portions chat-id]]})))
(register-handler-db
:set-contact-identity-from-qr
(fn [db [_ _ contact-identity]]
(assoc db :contacts/new-identity contact-identity)))
(register-handler-fx
:contact-update-received
(fn [{:keys [db]} [_ {:keys [from payload]}]]
(let [{:keys [chats current-public-key]} db]
(when (not= current-public-key from)
(let [{:keys [content timestamp]} payload
{:keys [status name profile-image]} (:profile content)
prev-last-updated (get-in db [:contacts/contacts from :last-updated])]
(when (<= prev-last-updated timestamp)
(let [contact {:whisper-identity from
:name name
:photo-path profile-image
:status status
:last-updated timestamp}]
{:dispatch-n (concat [[:update-contact! contact]]
(when (chats from)
[[:update-chat! {:chat-id from
:name name}]]))})))))))
(register-handler-fx
:update-keys-received
(fn [{:keys [db]} [_ {:keys [from payload]}]]
(let [{{:keys [public private]} :keypair
timestamp :timestamp} payload
prev-last-updated (get-in db [:contacts/contacts from :keys-last-updated])]
(when (<= prev-last-updated timestamp)
(let [contact {:whisper-identity from
:public-key public
:private-key private
:keys-last-updated timestamp}]
{:dispatch [:update-contact! contact]})))))
(register-handler-fx
:contact-online-received
(fn [{:keys [db]} [_ {:keys [from]
{{:keys [timestamp]} :content} :payload}]]
(let [prev-last-online (get-in db [:contacts/contacts from :last-online])]
(when (and timestamp (< prev-last-online timestamp))
{::reset-pending-messages from
:dispatch [:update-contact! {:whisper-identity from
:last-online timestamp}]}))))
(register-handler-fx
:hide-contact
(fn [{:keys [db]} [_ {:keys [whisper-identity] :as contact}]]
{::stop-watching-contact (merge
(select-keys db [:web3])
(select-keys contact [:whisper-identity]))
:dispatch-n [[:update-contact! {:whisper-identity whisper-identity
:pending? true}]
[:account-update-keys]]}))
(defn remove-contact-from-group [whisper-identity]
(fn [contacts]
(remove #(= whisper-identity (:identity %)) contacts)))
(register-handler-fx
:remove-contact-from-group
(fn [{:keys [db]} [_ whisper-identity group-id]]
(let [{:keys [contact-groups]} db
group' (update (contact-groups group-id) :contacts (remove-contact-from-group whisper-identity))]
{:dispatch [:update-group group']})))
(register-handler-fx
:remove-contact
(fn [{:keys [db]} [_ whisper-identity pred]]
(let [contact (get-in db [:contacts/contacts whisper-identity])]
(when (and contact (pred contact))
{:db (update db :contacts/contacts dissoc whisper-identity)
::delete-contact contact}))))
(register-handler-fx
:open-contact-toggle-list
(fn [{:keys [db]} [_ group-type]]
{:db (-> db
(assoc :group-type group-type
:selected-contacts #{}
:new-chat-name "")
(assoc-in [:toolbar-search :show] nil)
(assoc-in [:toolbar-search :text] ""))
:dispatch [:navigate-to :contact-toggle-list]}))
(register-handler-fx
:open-chat-with-contact
(fn [_ [_ {:keys [whisper-identity dapp?] :as contact}]]
{:dispatch-n (concat
[[:navigate-to-clean :chat-list]
[:start-chat whisper-identity {}]]
(when-not dapp?
[[::send-contact-request contact]]))}))
(register-handler-fx
:add-contact-handler
(fn [{:keys [db]} [_ id]]
(if (v/is-address? id)
{::request-contacts-by-address id}
{:dispatch (if (get-in db [:contacts/contacts id])
[:add-pending-contact id]
[:add-new-contact-and-open-chat {:name (generate-gfy)
:photo-path (identicon id)
:whisper-identity id}])})))

View File

@ -1,409 +0,0 @@
(ns status-im.contacts.handlers
(:require [re-frame.core :refer [after dispatch]]
[status-im.utils.handlers :refer [register-handler]]
[status-im.data-store.contacts :as contacts]
[status-im.utils.crypt :refer [encrypt]]
[clojure.string :as s]
[status-im.protocol.core :as protocol]
[status-im.utils.utils :refer [http-post]]
[status-im.utils.phone-number :refer [format-phone-number]]
[status-im.utils.handlers :as u]
[status-im.navigation.handlers :as nav]
[status-im.utils.random :as random]
[status-im.i18n :refer [label]]
[taoensso.timbre :as log]
[cljs.reader :refer [read-string]]
[status-im.utils.js-resources :as js-res]
[status-im.react-native.js-dependencies :as rn-dependencies]))
(defmethod nav/preload-data! :group-contacts
[db [_ _ group show-search?]]
(-> db
(assoc :contacts-group group)
(update :toolbar-search assoc
:show (when show-search? :contact-list)
:text "")))
(defmethod nav/preload-data! :edit-group
[db [_ _ group group-type]]
(if group
(assoc db :contact-group-id (:group-id group)
:group-type group-type
:new-chat-name (:name group))
db))
(defmethod nav/preload-data! :contact-list
[db [_ _ click-handler]]
(-> db
(assoc-in [:toolbar-search :show] nil)
(assoc-in [:contact-list-ui-props :edit?] false)
(assoc-in [:contacts-ui-props :edit?] false)
(assoc :contacts-click-handler click-handler)))
(defmethod nav/preload-data! :reorder-groups
[db [_ _]]
(assoc db :groups-order (->> (vals (:contact-groups db))
(remove :pending?)
(sort-by :order >)
(map :group-id))))
(register-handler :remove-contacts-click-handler
(fn [db]
(dissoc db
:contacts-click-handler
:contacts-click-action)))
(defn save-contact
[_ [_ contact]]
(contacts/save contact))
(defn watch-contact
[{:keys [web3]} [_ {:keys [whisper-identity public-key private-key]}]]
(when (and public-key private-key)
(protocol/watch-user! {:web3 web3
:identity whisper-identity
:keypair {:public public-key
:private private-key}
:callback #(dispatch [:incoming-message %1 %2])})))
(register-handler :watch-contact (u/side-effect! watch-contact))
(defn stop-watching-contact
[{:keys [web3]} [_ {:keys [whisper-identity]}]]
(protocol/stop-watching-user! {:web3 web3
:identity whisper-identity}))
(register-handler :stop-watching-contact (u/side-effect! stop-watching-contact))
(defn send-contact-request
[{:keys [current-public-key web3 current-account-id accounts]} [_ contact]]
(let [{:keys [whisper-identity]} contact
{:keys [name photo-path updates-public-key updates-private-key status]}
(get accounts current-account-id)]
(protocol/contact-request!
{:web3 web3
:message {:from current-public-key
:to whisper-identity
:message-id (random/id)
:payload {:contact {:name name
:profile-image photo-path
:address current-account-id
:status status}
:keypair {:public updates-public-key
:private updates-private-key}}}})))
(register-handler :send-contact-request! (u/side-effect! send-contact-request))
(register-handler :update-contact!
(fn [db [_ {:keys [whisper-identity] :as contact}]]
(if (contacts/exists? whisper-identity)
(do
(contacts/save contact)
(update-in db [:contacts whisper-identity] merge contact))
db)))
(defn load-contacts! [db _]
(let [contacts-list (->> (contacts/get-all)
(map (fn [{:keys [whisper-identity] :as contact}]
[whisper-identity contact])))
global-commands (->> contacts-list
(filter (fn [[_ c]] (:global-command c)))
(map (fn [[id {:keys [global-command]}]]
[(keyword id) (-> global-command
(update :params (comp vec vals))
(assoc :bot id
:type :command))]))
(into {}))
contacts (into {} contacts-list)]
(doseq [[_ contact] contacts]
(dispatch [:watch-contact contact]))
(assoc db :contacts contacts
:global-commands global-commands)))
(register-handler :load-contacts load-contacts!)
(defn contact-name [contact]
(->> contact
((juxt :givenName :middleName :familyName))
(remove s/blank?)
(s/join " ")))
(defn normalize-phone-contacts [contacts]
(let [contacts' (js->clj contacts :keywordize-keys true)]
(map (fn [{:keys [thumbnailPath phoneNumbers] :as contact}]
{:name (contact-name contact)
:photo-path thumbnailPath
:phone-numbers phoneNumbers}) contacts')))
(defn fetch-contacts-from-phone!
[_ _]
(.getAll rn-dependencies/contacts
(fn [error contacts]
(if error
(log/debug :error-on-fetching-loading error)
(let [contacts' (normalize-phone-contacts contacts)]
(dispatch [:get-contacts-identities contacts']))))))
(register-handler :sync-contacts
(u/side-effect! fetch-contacts-from-phone!))
(defn get-contacts-by-hash [contacts]
(->> contacts
(mapcat (fn [{:keys [phone-numbers] :as contact}]
(map (fn [{:keys [number]}]
(let [number' (format-phone-number number)]
[(encrypt number')
(-> contact
(assoc :phone-number number')
(dissoc :phone-numbers))]))
phone-numbers)))
(into {})))
(defn add-identity [contacts-by-hash contacts]
(map (fn [{:keys [phone-number-hash whisper-identity address]}]
(let [contact (contacts-by-hash phone-number-hash)]
(assoc contact :whisper-identity whisper-identity
:address address)))
(js->clj contacts)))
(defn request-stored-contacts [contacts]
(let [contacts-by-hash (get-contacts-by-hash contacts)
data (or (keys contacts-by-hash) ())]
(http-post "get-contacts" {:phone-number-hashes data}
(fn [{:keys [contacts]}]
(let [contacts' (add-identity contacts-by-hash contacts)]
(dispatch [:add-contacts contacts']))))))
(defn get-identities-by-contacts! [_ [_ contacts]]
(request-stored-contacts contacts))
(register-handler :get-contacts-identities
(u/side-effect! get-identities-by-contacts!))
(defn add-contacts-to-groups [{:keys [new-contacts]} _]
(let [default-contacts js-res/default-contacts]
(doseq [{:keys [whisper-identity]} new-contacts]
(let [groups (:groups ((keyword whisper-identity) default-contacts))]
(doseq [group groups]
(dispatch [:add-contacts-to-group group [whisper-identity]]))))))
(defn save-contacts! [{:keys [new-contacts]} _]
(contacts/save-all new-contacts))
(defn update-pending-status [old-contacts {:keys [whisper-identity pending?] :as contact}]
(let [{old-pending :pending?
:as old-contact} (get old-contacts whisper-identity)
pending?' (if old-contact (and old-pending pending?) pending?)]
(assoc contact :pending? (boolean pending?'))))
(defn add-new-contacts
[{:keys [contacts] :as db} [_ new-contacts]]
(let [identities (set (keys contacts))
new-contacts' (->> new-contacts
(map #(update-pending-status contacts %))
(remove #(identities (:whisper-identity %)))
(map #(vector (:whisper-identity %) %))
(into {}))
global-commands (->> new-contacts'
(keep (fn [[n {:keys [global-command]}]]
(when global-command
[(keyword n) (assoc global-command
:type :command
:bot n)])))
(into {}))]
(-> db
(update :global-commands merge global-commands)
(update :contacts merge new-contacts')
(assoc :new-contacts (vals new-contacts')))))
(defn public-key->address [public-key]
(let [length (count public-key)
normalized-key (case length
132 (subs public-key 4)
130 (subs public-key 2)
128 public-key
nil)]
(when normalized-key
(subs (.sha3 js/Web3.prototype normalized-key #js {:encoding "hex"}) 26))))
(register-handler :load-default-contacts!
(u/side-effect!
(fn [{:keys [contacts groups]}]
(let [default-contacts js-res/default-contacts
default-groups js-res/default-contact-groups]
(dispatch [:add-groups (mapv
(fn [[id {:keys [name contacts]}]]
{:group-id (clojure.core/name id)
:name (:en name)
:order 0
:timestamp (random/timestamp)
:contacts (mapv #(hash-map :identity %) contacts)})
default-groups)])
(doseq [[id {:keys [name photo-path public-key add-chat? global-command
dapp? dapp-url dapp-hash bot-url unremovable?]}] default-contacts]
(let [id' (clojure.core/name id)]
(when-not (get contacts id')
(when add-chat?
(dispatch [:add-chat id' {:name (:en name)}]))
(let [contact
{:whisper-identity id'
:address (public-key->address id')
:name (:en name)
:photo-path photo-path
:public-key public-key
:unremovable? (boolean unremovable?)
:dapp? dapp?
:dapp-url (:en dapp-url)
:bot-url bot-url
:global-command global-command
:dapp-hash dapp-hash}]
(dispatch [:add-contacts [contact]])))
(when bot-url
(dispatch [:load-commands! id']))))))))
(register-handler :add-contacts
[(after save-contacts!)
(after add-contacts-to-groups)]
add-new-contacts)
(defn add-new-contact [db [_ {:keys [whisper-identity] :as contact}]]
(-> db
(update-in [:contacts whisper-identity] merge contact)
(assoc :new-contact-identity "")))
(register-handler :add-new-contact
(u/side-effect!
(fn [_ [_ {:keys [whisper-identity] :as contact}]]
(when-not (contacts/get-by-id whisper-identity)
(let [contact (assoc contact :address (public-key->address whisper-identity))]
(dispatch [::prepare-contact contact]))
(dispatch [:start-chat whisper-identity {} :navigation-replace])))))
(register-handler ::prepare-contact
(u/handlers->
add-new-contact
save-contact
send-contact-request))
(register-handler ::update-pending-contact
(after save-contact)
add-new-contact)
(register-handler :add-pending-contact
(u/side-effect!
(fn [{:keys [chats contacts]} [_ chat-id]]
(let [contact (if-let [contact-info (get-in chats [chat-id :contact-info])]
(read-string contact-info)
(assoc (get contacts chat-id) :pending? false))
contact' (assoc contact :address (public-key->address chat-id)
:pending? false)]
(dispatch [::prepare-contact contact'])
(dispatch [:watch-contact contact'])
(dispatch [:discoveries-send-portions chat-id])))))
(defn set-contact-identity-from-qr
[db [_ _ contact-identity]]
(assoc db :new-contact-identity contact-identity))
(register-handler :set-contact-identity-from-qr set-contact-identity-from-qr)
(register-handler :contact-update-received
(u/side-effect!
(fn [{:keys [chats current-public-key] :as db} [_ {:keys [from payload]}]]
(when (not= current-public-key from)
(let [{:keys [content timestamp]} payload
{:keys [status name profile-image]} (:profile content)
prev-last-updated (get-in db [:contacts from :last-updated])]
(when (<= prev-last-updated timestamp)
(let [contact {:whisper-identity from
:name name
:photo-path profile-image
:status status
:last-updated timestamp}]
(dispatch [:update-contact! contact])
(when (chats from)
(dispatch [:update-chat! {:chat-id from
:name name}])))))))))
(register-handler :update-keys-received
(u/side-effect!
(fn [db [_ {:keys [from payload]}]]
(let [{{:keys [public private]} :keypair
timestamp :timestamp} payload
prev-last-updated (get-in db [:contacts from :keys-last-updated])]
(when (<= prev-last-updated timestamp)
(let [contact {:whisper-identity from
:public-key public
:private-key private
:keys-last-updated timestamp}]
(dispatch [:update-contact! contact])))))))
(register-handler :contact-online-received
(u/side-effect!
(fn [db [_ {:keys [from]
{{:keys [timestamp]} :content} :payload}]]
(let [prev-last-online (get-in db [:contacts from :last-online])]
(when (and timestamp (< prev-last-online timestamp))
(protocol/reset-pending-messages! from)
(dispatch [:update-contact! {:whisper-identity from
:last-online timestamp}]))))))
(register-handler :hide-contact
(after stop-watching-contact)
(u/side-effect!
(fn [_ [_ {:keys [whisper-identity] :as contact}]]
(dispatch [:update-contact! {:whisper-identity whisper-identity
:pending? true}])
(dispatch [:account-update-keys]))))
(defn remove-contact-from-group [whisper-identity]
(fn [contacts]
(remove #(= whisper-identity (:identity %)) contacts)))
(register-handler :remove-contact-from-group
(u/side-effect!
(fn [{:keys [contact-groups]} [_ whisper-identity group-id]]
(let [group' (update (contact-groups group-id) :contacts (remove-contact-from-group whisper-identity))]
(dispatch [:update-group group'])))))
(register-handler :remove-contact
(fn [db [_ whisper-identity pred]]
(if-let [contact (contacts/get-by-id whisper-identity)]
(if (pred contact)
(do
(contacts/delete contact)
(update db :contacts dissoc whisper-identity))
db)
db)))
(register-handler :open-contact-menu
(u/side-effect!
(fn [_ [_ list-selection-fn {:keys [name] :as contact}]]
(list-selection-fn {:title name
:options [(label :t/remove-contact)]
:callback (fn [index]
(case index
0 (dispatch [:hide-contact contact])
:default))
:cancel-text (label :t/cancel)}))))
(register-handler :open-contact-toggle-list
(after #(dispatch [:navigate-to :contact-toggle-list]))
(fn [db [_ group-type]]
(->
(assoc db :group-type group-type
:selected-contacts #{}
:new-chat-name "")
(assoc-in [:toolbar-search :show] nil)
(assoc-in [:toolbar-search :text] ""))))
(register-handler :open-chat-with-contact
(u/side-effect!
(fn [_ [_ {:keys [whisper-identity dapp?] :as contact}]]
(when-not dapp?
(dispatch [:send-contact-request! contact]))
(dispatch [:navigate-to-clean :chat-list])
(dispatch [:start-chat whisper-identity {}]))))

View File

@ -0,0 +1,33 @@
(ns status-im.contacts.navigation
(:require [status-im.navigation.handlers :as nav]))
(defmethod nav/preload-data! :group-contacts
[db [_ _ group show-search?]]
(-> db
(assoc :contacts-group group)
(update :toolbar-search assoc
:show (when show-search? :contact-list)
:text "")))
(defmethod nav/preload-data! :edit-group
[db [_ _ group group-type]]
(if group
(assoc db :contact-group-id (:group-id group)
:group-type group-type
:new-chat-name (:name group))
db))
(defmethod nav/preload-data! :contact-list
[db [_ _ click-handler]]
(-> db
(assoc-in [:toolbar-search :show] nil)
(assoc-in [:contacts/list-ui-props :edit?] false)
(assoc-in [:contacts/ui-props :edit?] false)
(assoc :contacts/click-handler click-handler)))
(defmethod nav/preload-data! :reorder-groups
[db [_ _]]
(assoc db :groups-order (->> (vals (:contact-groups db))
(remove :pending?)
(sort-by :order >)
(map :group-id))))

View File

@ -0,0 +1,88 @@
(ns status-im.contacts.new-contact.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [dispatch]]
[clojure.string :as str]
[status-im.components.react :refer [view text]]
[status-im.components.text-field.view :refer [text-field]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar.view :refer [toolbar]]
[status-im.components.toolbar.actions :as act]
[status-im.components.toolbar.styles :refer [toolbar-title-container
toolbar-title-text
toolbar-background1]]
[status-im.components.styles :refer [icon-ok button-input-container button-input color-blue]]
[status-im.components.image-button.view :refer [scan-button]]
[status-im.i18n :refer [label]]
[cljs.spec.alpha :as s]
[status-im.contacts.styles :as st]
[status-im.utils.hex :refer [normalize-hex]]
[status-im.utils.platform :refer [platform-specific]]
[status-im.contacts.db :as v]))
(def toolbar-title
[view toolbar-title-container
[text {:style toolbar-title-text}
(label :t/add-new-contact)]])
(defn- validation-error-message
[whisper-identity {:keys [address public-key]} error]
(cond
(#{(normalize-hex address) (normalize-hex public-key)}
(normalize-hex whisper-identity))
(label :t/can-not-add-yourself)
(not (s/valid? ::v/public-key whisper-identity))
(label :t/enter-valid-public-key)
(not (v/contact-can-be-added? whisper-identity))
(label :t/contact-already-added)
:else error))
(defn toolbar-actions [new-contact-identity account error]
(let [error-message (validation-error-message new-contact-identity account error)]
[{:image {:source {:uri (if (str/blank? error-message)
:icon_ok_blue
:icon_ok_disabled)}
:style icon-ok}
:handler #(when (str/blank? error-message)
(dispatch [:add-contact-handler new-contact-identity]))}]))
(defview contact-whisper-id-input [whisper-identity error]
(letsubs [current-account [:get-current-account]]
(let [error (when-not (str/blank? whisper-identity)
(validation-error-message whisper-identity current-account error))]
[view button-input-container
[text-field
{:error error
:error-color color-blue
:input-style st/qr-input
:value whisper-identity
:wrapper-style button-input
:label (label :t/public-key)
:on-change-text #(do
(dispatch [:set :contacts/new-identity %])
(dispatch [:set :contacts/new-public-key-error nil]))}]
[scan-button {:show-label? (zero? (count whisper-identity))
:handler #(dispatch [:scan-qr-code
{:toolbar-title (label :t/new-contact)}
:set-contact-identity-from-qr])}]])))
(defview new-contact []
(letsubs [new-contact-identity [:get :contacts/new-identity]
error [:get :contacts/new-public-key-error]
account [:get-current-account]]
[view st/contact-form-container
[status-bar]
[toolbar {:background-color toolbar-background1
:style (get-in platform-specific [:component-styles :toolbar])
:nav-action (act/back #(dispatch [:navigate-back]))
:title (label :t/add-new-contact)
:actions (toolbar-actions new-contact-identity account error)}]
[view st/form-container
[contact-whisper-id-input new-contact-identity error]]
[view st/address-explication-container
[text {:style st/address-explication
:font :default}
(label :t/address-explication)]]]))

View File

@ -1,15 +0,0 @@
(ns status-im.contacts.specs
(:require [cljs.spec.alpha :as s]))
(s/def :contacts/contacts (s/nilable map?)) ;; {id (string) contact (map)}
(s/def :contacts/new-contacts (s/nilable seq?))
(s/def :contacts/new-contact-identity (s/nilable string?)) ;;public key of new contact during adding this new contact
(s/def :contacts/new-contact-public-key-error (s/nilable string?))
(s/def :contacts/contact-identity (s/nilable string?)) ;;on showing this contact profile
(s/def :contacts/contacts-ui-props (s/nilable map?))
(s/def :contacts/contact-list-ui-props (s/nilable map?))
(s/def :contacts/contacts-click-handler (s/nilable fn?)) ;;used in modal list (for example for wallet)
(s/def :contacts/contacts-click-action (s/nilable keyword?)) ;;used in modal list (for example for wallet)
(s/def :contacts/contacts-click-params (s/nilable map?)) ;;used in modal list (for example for wallet)

View File

@ -3,8 +3,6 @@
(:require [status-im.components.styles :as common]
[status-im.components.tabs.styles :as tabs-st]))
;; Contacts list
(def toolbar-actions
{:flex-direction :row})

View File

@ -4,13 +4,15 @@
[clojure.string :as str]
[status-im.bots.constants :as bots-constants]))
(reg-sub :current-contact
(reg-sub
:current-contact
(fn [db [_ k]]
(get-in db [:contacts (:current-chat-id db) k])))
(get-in db [:contacts/contacts (:current-chat-id db) k])))
(reg-sub :get-contacts
(reg-sub
:get-contacts
(fn [db _]
(:contacts db)))
(:contacts/contacts db)))
(defn sort-contacts [contacts]
(sort (fn [c1 c2]
@ -20,74 +22,93 @@
(clojure.string/lower-case name2))))
(vals contacts)))
(reg-sub
:all-added-contacts
:<- [:get-contacts]
(fn [contacts]
(->> (remove (fn [[_ {:keys [pending? whisper-identity]}]]
(or (true? pending?)
(bots-constants/hidden-bots whisper-identity))) contacts)
(sort-contacts))))
(reg-sub :all-added-contacts
(fn [db]
(let [contacts (:contacts db)]
(->> (remove (fn [[_ {:keys [pending? whisper-identity]}]]
(or (true? pending?)
(bots-constants/hidden-bots whisper-identity))) contacts)
(sort-contacts)))))
(reg-sub :all-added-people-contacts
(reg-sub
:all-added-people-contacts
:<- [:all-added-contacts]
(fn [contacts]
(remove #(true? (:dapp? %)) contacts)))
(reg-sub :people-in-current-chat
(fn [{:keys [current-chat-id]} _]
(let [contacts (subscribe [:current-chat-contacts])]
(remove #(true? (:dapp? %)) @contacts))))
(reg-sub
:people-in-current-chat
:<- [:current-chat-contacts]
(fn [contacts]
(remove #(true? (:dapp? %)) contacts)))
(defn filter-group-contacts [group-contacts contacts]
(filter #(group-contacts (:whisper-identity %)) contacts))
(let [group-contacts' (into #{} (map #(:identity %) group-contacts))]
(filter #(group-contacts' (:whisper-identity %)) contacts)))
(reg-sub :all-added-group-contacts
(reg-sub
:group-contacts
(fn [db [_ group-id]]
(let [contacts (subscribe [:all-added-contacts])
group-contacts (into #{} (map #(:identity %)
(get-in db [:contact-groups group-id :contacts])))]
(filter-group-contacts group-contacts @contacts))))
(get-in db [:contact-groups group-id :contacts])))
(reg-sub
:all-added-group-contacts
(fn [[_ group-id] _]
[(subscribe [:all-added-contacts])
(subscribe [:group-contacts group-id])])
(fn [[contacts group-contacts] _]
(filter-group-contacts group-contacts contacts)))
(defn filter-not-group-contacts [group-contacts contacts]
(remove #(group-contacts (:whisper-identity %)) contacts))
(let [group-contacts' (into #{} (map #(:identity %) group-contacts))]
(remove #(group-contacts' (:whisper-identity %)) contacts)))
(reg-sub :all-not-added-group-contacts
(fn [db [_ group-id]]
(let [contacts (subscribe [:all-added-contacts])
group-contacts (into #{} (map #(:identity %)
(get-in db [:contact-groups group-id :contacts])))]
(filter-not-group-contacts group-contacts @contacts))))
(reg-sub
:all-not-added-group-contacts
(fn [[_ group-id] _]
[(subscribe [:all-added-contacts])
(subscribe [:group-contacts group-id])])
(fn [[contacts group-contacts]]
(filter-not-group-contacts group-contacts contacts)))
(reg-sub :all-added-group-contacts-with-limit
(fn [db [_ group-id limit]]
(let [contacts (subscribe [:all-added-group-contacts group-id])]
(take limit @contacts))))
(reg-sub
:all-added-group-contacts-with-limit
(fn [[_ group-id limit] _]
(subscribe [:all-added-group-contacts group-id]))
(fn [contacts [_ group-id limit]]
(take limit contacts)))
(reg-sub :all-added-group-contacts-count
(fn [_ [_ group-id]]
(let [contacts (subscribe [:all-added-group-contacts group-id])]
(count @contacts))))
(reg-sub
:all-added-group-contacts-count
(fn [[_ group-id] _]
(subscribe [:all-added-group-contacts group-id]))
(fn [contacts _]
(count contacts)))
(reg-sub :get-added-contacts-with-limit
(reg-sub
:get-added-contacts-with-limit
:<- [:all-added-contacts]
(fn [contacts [_ limit]]
(take limit contacts)))
(reg-sub :added-contacts-count
(reg-sub
:added-contacts-count
:<- [:all-added-contacts]
(fn [contacts]
(count contacts)))
(reg-sub :all-added-groups
(reg-sub
:contact-groups
(fn [db]
(let [groups (vals (:contact-groups db))]
(->> (remove :pending? groups)
(sort-by :order >)))))
(vals (:contact-groups db))))
(defn get-contact-letter [contact]
(when-let [letter (first (:name contact))]
(clojure.string/upper-case letter)))
(reg-sub
:all-added-groups
:<- [:contact-groups]
(fn [groups]
(->> (remove :pending? groups)
(sort-by :order >))))
(defn search-filter [text item]
(let [name (-> (or (:name item) "")
@ -95,92 +116,126 @@
text (str/lower-case text)]
(not= (str/index-of name text) nil)))
(defn search-filter-reaction [contacts]
(let [text (subscribe [:get-in [:toolbar-search :text]])]
(if @text
(filter #(search-filter @text %) @contacts)
@contacts)))
(defn search-filter-reaction [contacts text]
(if text
(filter #(search-filter text %) contacts)
contacts))
(reg-sub :all-added-group-contacts-filtered
(fn [_ [_ group-id]]
(let [contacts (if group-id
(subscribe [:all-added-group-contacts group-id])
(subscribe [:all-added-contacts]))]
(search-filter-reaction contacts))))
(reg-sub
:all-added-group-contacts-filtered
(fn [[_ group-id] _]
[(if group-id
(subscribe [:all-added-group-contacts group-id])
(subscribe [:all-added-contacts]))
(subscribe [:get-in [:toolbar-search :text]])])
(fn [[contacts text] _]
(search-filter-reaction contacts text)))
(reg-sub :all-group-not-added-contacts-filtered
(reg-sub
:contact-group-contacts
(fn [db]
(let [contact-group-id (:contact-group-id db)
contacts (subscribe [:all-not-added-group-contacts contact-group-id])]
(search-filter-reaction contacts))))
(get-in db [:contact-groups (:contact-group-id db) :contacts])))
(reg-sub :contacts-filtered
(fn [_ [_ subscription-id]]
(let [contacts (subscribe [subscription-id])]
(search-filter-reaction contacts))))
(reg-sub
:all-not-added-contact-group-contacts
(fn [_ _]
[(subscribe [:all-added-contacts])
(subscribe [:contact-group-contacts])])
(fn [[contacts group-contacts]]
(filter-not-group-contacts group-contacts contacts)))
(reg-sub :contacts-with-letters
(reg-sub
:all-group-not-added-contacts-filtered
(fn [_ _]
[(subscribe [:all-not-added-contact-group-contacts])
(subscribe [:get-in [:toolbar-search :text]])])
(fn [[contacts text] _]
(search-filter-reaction contacts text)))
(reg-sub
:contacts-filtered
(fn [[_ subscription-id] _]
[(subscribe [subscription-id])
(subscribe [:get-in [:toolbar-search :text]])])
(fn [[contacts text]]
(search-filter-reaction contacts text)))
(reg-sub
:contact
(fn [db]
(let [contacts (:contacts db)]
(let [ordered (sort-contacts contacts)]
(reduce (fn [prev cur]
(let [prev-letter (get-contact-letter (last prev))
cur-letter (get-contact-letter cur)]
(conj prev
(if (not= prev-letter cur-letter)
(assoc cur :letter cur-letter)
cur))))
[] ordered)))))
(let [identity (:contacts/identity db)]
(get-in db [:contacts/contacts identity]))))
(defn contacts-by-chat [fn db chat-id]
(let [chat (get-in db [:chats chat-id])
contacts (:contacts db)]
(when chat
(let [current-participants (->> chat
:contacts
(map :identity)
set)]
(fn #(current-participants (:whisper-identity %))
(vals contacts))))))
(defn contacts-by-current-chat [fn db]
(let [current-chat-id (:current-chat-id db)]
(contacts-by-chat fn db current-chat-id)))
(reg-sub :contact
(fn [db]
(let [identity (:contact-identity db)]
(get-in db [:contacts identity]))))
(reg-sub :contact-by-identity
(reg-sub
:contact-by-identity
(fn [db [_ identity]]
(get-in db [:contacts identity])))
(get-in db [:contacts/contacts identity])))
(reg-sub :contact-name-by-identity
(reg-sub
:contact-name-by-identity
:<- [:get-contacts]
(fn [contacts [_ identity]]
(:name (contacts identity))))
(reg-sub :all-new-contacts
(fn [db]
(contacts-by-current-chat remove db)))
(reg-sub :current-chat-contacts
(fn [db]
(contacts-by-current-chat filter db)))
(reg-sub :chat-photo
(reg-sub
:chat-by-id
(fn [db [_ chat-id]]
(let [chat-id (or chat-id (:current-chat-id db))
chat (get-in db [:chats chat-id])
contacts (contacts-by-chat filter db chat-id)]
(when (and chat (not (:group-chat chat)))
(cond
(:photo-path chat)
(:photo-path chat)
(get-in db [:chats chat-id])))
(pos? (count contacts))
(:photo-path (first contacts))
(reg-sub
:current-chat
(fn [db _]
(get-in db [:chats (:current-chat-id db)])))
:else
(identicon chat-id))))))
(defn chat-contacts [[chat contacts] [_ fn]]
(when chat
(let [current-participants (->> chat
:contacts
(map :identity)
set)]
(fn #(current-participants (:whisper-identity %))
(vals contacts)))))
(reg-sub
:contacts-current-chat
:<- [:current-chat]
:<- [:get-contacts]
chat-contacts)
(reg-sub
:all-new-contacts
:<- [:contacts-current-chat remove]
(fn [contacts]
contacts))
(reg-sub
:current-chat-contacts
:<- [:contacts-current-chat filter]
(fn [contacts]
contacts))
(reg-sub
:contacts-by-chat
(fn [[_ fn chat-id] _]
[(subscribe [:chat-by-id chat-id])
(subscribe [:get-contacts])])
chat-contacts)
(reg-sub
:chat-photo
(fn [[_ chat-id] _]
[(if chat-id
(subscribe [:chat-by-id chat-id])
(subscribe [:current-chat]))
(subscribe [:contacts-by-chat filter chat-id])])
(fn [[chat contacts] [_ chat-id]]
(when (and chat (not (:group-chat chat)))
(cond
(:photo-path chat)
(:photo-path chat)
(pos? (count contacts))
(:photo-path (first contacts))
:else
(identicon chat-id)))))

View File

@ -1,39 +0,0 @@
(ns status-im.contacts.validations
(:require [cljs.spec.alpha :as s]
[clojure.string :as str]
[status-im.data-store.contacts :as contacts]
[status-im.js-dependencies :as dependencies]))
(defn is-address? [s]
(.isAddress dependencies/Web3.prototype s))
(defn contact-can-be-added? [identity]
(if (contacts/exists? identity)
(:pending? (contacts/get-by-id identity))
true))
(defn hex-string? [s]
(let [s' (if (str/starts-with? s "0x")
(subs s 2)
s)]
(boolean (re-matches #"(?i)[0-9a-f]+" s'))))
(defn valid-length? [identity]
(let [length (count identity)]
(and
(hex-string? identity)
(or
(and (= 128 length) (not (str/includes? identity "0x")))
(and (= 130 length) (str/starts-with? identity "0x"))
(and (= 132 length) (str/starts-with? identity "0x04"))
(is-address? identity)))))
(s/def ::identity-length valid-length?)
(s/def ::contact-can-be-added contact-can-be-added?)
(s/def ::not-empty-string (s/and string? not-empty))
(s/def ::name ::not-empty-string)
(s/def ::whisper-identity (s/and ::not-empty-string
::identity-length))
(s/def ::contact (s/keys :req-un [::name ::whisper-identity]
:opt-un [::phone ::photo-path ::address]))

View File

@ -1,23 +1,14 @@
(ns status-im.contacts.screen
(:require-macros [status-im.utils.views :refer [defview]])
(ns status-im.contacts.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.common.common :as common]
[status-im.components.react :refer [view
text
image
icon
touchable-highlight
scroll-view
list-view
list-item]]
[status-im.components.react :refer [view text icon touchable-highlight scroll-view]]
[status-im.components.native-action-button :refer [native-action-button
native-action-button-item]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar]]
[status-im.components.toolbar-new.actions :as act]
[status-im.components.drawer.view :refer [open-drawer]]
[status-im.components.icons.custom-icons :refer [ion-icon]]
[status-im.components.context-menu :refer [context-menu]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.utils.platform :refer [platform-specific ios? android?]]
[status-im.utils.utils :as u]
@ -26,11 +17,11 @@
[status-im.components.styles :refer [color-blue
create-icon]]))
(def contacts-limit 5)
(def ^:const contacts-limit 5)
(def toolbar-options
[{:text (label :t/new-contact) :value #(dispatch [:navigate-to :new-contact])}
{:text (label :t/edit) :value #(dispatch [:set-in [:contacts-ui-props :edit?] true])}
{:text (label :t/edit) :value #(dispatch [:set-in [:contacts/ui-props :edit?] true])}
{:text (label :t/new-group) :value #(dispatch [:open-contact-toggle-list :contact-group])}
{:text (label :t/reorder-groups) :value #(dispatch [:navigate-to :reorder-groups])}])
@ -39,23 +30,23 @@
(act/opts (if ios? toolbar-options (rest toolbar-options)))])
(defn toolbar-view []
[toolbar {:title (label :t/contacts)
:nav-action (act/hamburger open-drawer)
:actions (toolbar-actions)}])
[toolbar {:title (label :t/contacts)
:nav-action (act/hamburger open-drawer)
:actions (toolbar-actions)}])
(defn toolbar-edit []
[toolbar {:nav-action (act/back #(dispatch [:set-in [:contacts-ui-props :edit?] false]))
:actions [{:image :blank}]
:title (label :t/edit-contacts)}])
[toolbar {:nav-action (act/back #(dispatch [:set-in [:contacts/ui-props :edit?] false]))
:actions [{:image :blank}]
:title (label :t/edit-contacts)}])
(defn contact-options [{:keys [unremovable?] :as contact} group]
(let [delete-contact-opt {:value #(u/show-confirmation
(str (label :t/delete-contact) "?") (label :t/delete-contact-confirmation)
(label :t/delete)
(fn[] (dispatch [:hide-contact contact])))
(fn [] (dispatch [:hide-contact contact])))
:text (label :t/delete-contact)
:destructive? true}
options (if unremovable? [] [delete-contact-opt])]
options (if unremovable? [] [delete-contact-opt])]
(if group
(conj options
{:value #(dispatch [:remove-contact-from-group
@ -69,10 +60,10 @@
[view
(when subtitle
[common/form-title subtitle
{:count-value contacts-count
:extended? edit?
:options [{:value #(dispatch [:navigate-to :edit-group group :contact-group])
:text (label :t/edit-group)}]}])
{:count-value contacts-count
:extended? edit?
:options [{:value #(dispatch [:navigate-to :edit-group group :contact-group])
:text (label :t/edit-group)}]}])
[view st/contacts-list
[common/list-footer]
(doall
@ -93,20 +84,20 @@
[view st/show-all
[touchable-highlight {:on-press #(do
(when edit?
(dispatch [:set-in [:contact-list-ui-props :edit?] true]))
(dispatch [:set-in [:contacts/list-ui-props :edit?] true]))
(dispatch [:navigate-to :group-contacts group]))}
[view
[text {:style st/show-all-text
[text {:style st/show-all-text
:uppercase? (get-in platform-specific [:uppercase?])
:font (get-in platform-specific [:component-styles :contacts :show-all-text-font])}
:font (get-in platform-specific [:component-styles :contacts :show-all-text-font])}
(str (- contacts-count contacts-limit) " " (label :t/more))]]]]])
[common/bottom-shadow]]))
(defview contact-group-view [{:keys [group] :as params}]
[contacts [:all-added-group-contacts-with-limit (:group-id group) contacts-limit]
contacts-count [:all-added-group-contacts-count (:group-id group)]]
[contact-group-form (merge params {:contacts contacts
:contacts-count contacts-count})])
(letsubs [contacts [:all-added-group-contacts-with-limit (:group-id group) contacts-limit]
contacts-count [:all-added-group-contacts-count (:group-id group)]]
[contact-group-form (merge params {:contacts contacts
:contacts-count contacts-count})]))
(defn contacts-action-button []
[native-action-button {:button-color color-blue
@ -122,28 +113,28 @@
:style create-icon}]]])
(defview contact-list [_]
[contacts [:get-added-contacts-with-limit contacts-limit]
contacts-count [:added-contacts-count]
edit? [:get-in [:contacts-ui-props :edit?]]
groups [:all-added-groups]
tabs-hidden? [:tabs-hidden?]]
[view {:flex 1}
[view (st/contacts-list-container tabs-hidden?)
(if edit?
[toolbar-edit]
[toolbar-view])
(if (pos? (+ (count groups) contacts-count))
[scroll-view {:style st/contact-groups}
(when (pos? contacts-count)
[contact-group-form {:contacts contacts
:contacts-count contacts-count
:edit? edit?}])
(for [group groups]
^{:key group}
[contact-group-view {:group group
:edit? edit?}])]
[view st/empty-contact-groups
[icon :group_big st/empty-contacts-icon]
[text {:style st/empty-contacts-text} (label :t/no-contacts)]])]
(when (and android? (not edit?))
[contacts-action-button])])
(letsubs [contacts [:get-added-contacts-with-limit contacts-limit]
contacts-count [:added-contacts-count]
edit? [:get-in [:contacts/ui-props :edit?]]
groups [:all-added-groups]
tabs-hidden? [:tabs-hidden?]]
[view {:flex 1}
[view (st/contacts-list-container tabs-hidden?)
(if edit?
[toolbar-edit]
[toolbar-view])
(if (pos? (+ (count groups) contacts-count))
[scroll-view {:style st/contact-groups}
(when (pos? contacts-count)
[contact-group-form {:contacts contacts
:contacts-count contacts-count
:edit? edit?}])
(for [group groups]
^{:key group}
[contact-group-view {:group group
:edit? edit?}])]
[view st/empty-contact-groups
[icon :group_big st/empty-contacts-icon]
[text {:style st/empty-contacts-text} (label :t/no-contacts)]])]
(when (and android? (not edit?))
[contacts-action-button])]))

View File

@ -1,77 +0,0 @@
(ns status-im.contacts.views.contact-list
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[status-im.components.common.common :as common]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.contacts.screen :refer [contact-options]]
[status-im.components.react :refer [view text
image
icon
touchable-highlight
list-view
list-item]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar-with-search toolbar]]
[status-im.components.toolbar-new.actions :as act]
[status-im.components.toolbar-new.styles :refer [toolbar-background1]]
[status-im.components.drawer.view :refer [drawer-view open-drawer]]
[status-im.components.image-button.view :refer [scan-button]]
[status-im.contacts.styles :as st]
[status-im.utils.listview :as lw]
[status-im.i18n :refer [label]]
[status-im.utils.platform :refer [platform-specific]]))
(defn render-row [group edit?]
(fn [row _ _]
(list-item
^{:key row}
[contact-view {:contact row
:on-press #(dispatch [:open-chat-with-contact %])
:extended? edit?
:extend-options (contact-options row group)}])))
(defview contact-list-toolbar-edit [group]
[toolbar {:nav-action (act/back #(dispatch [:set-in [:contact-list-ui-props :edit?] false]))
:actions [{:image :blank}]
:title (if-not group
(label :t/contacts)
(or (:name group) (label :t/contacts-group-new-chat)))}])
(defview contact-list-toolbar [group]
[show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]]
(toolbar-with-search
{:show-search? (= show-search :contact-list)
:search-text search-text
:search-key :contact-list
:title (if-not group
(label :t/contacts)
(or (:name group) (label :t/contacts-group-new-chat)))
:search-placeholder (label :t/search-contacts)
:actions [(act/opts [{:text (label :t/edit)
:value #(dispatch [:set-in [:contact-list-ui-props :edit?] true])}])]}))
(defview contacts-list-view [group edit?]
[contacts [:all-added-group-contacts-filtered (:group-id group)]]
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow (render-row group edit?)
:keyboardShouldPersistTaps :always
:renderHeader renderers/list-header-renderer
:renderFooter renderers/list-footer-renderer
:renderSeparator renderers/list-separator-renderer
:style st/contacts-list}])
(defview contact-list []
[edit? [:get-in [:contact-list-ui-props :edit?]]
group [:get :contacts-group]
type [:get :group-type]]
[drawer-view
[view {:flex 1}
[view
[status-bar]
(if edit?
[contact-list-toolbar-edit group]
[contact-list-toolbar group])]
[contacts-list-view group edit?]]])

View File

@ -1,84 +0,0 @@
(ns status-im.contacts.views.contact-list-modal
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.common.common :as common]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.react :refer [view text
image
icon
touchable-highlight
list-view
list-item]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.components.action-button.action-button :refer [action-button
action-separator]]
[status-im.components.action-button.styles :refer [actions-list]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar-with-search toolbar]]
[status-im.components.toolbar-new.actions :as act]
[status-im.components.drawer.view :refer [drawer-view]]
[status-im.contacts.styles :as st]
[status-im.utils.listview :as lw]
[status-im.i18n :refer [label]]))
(defview contact-list-modal-toolbar []
[show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]]
(toolbar-with-search
{:show-search? (= show-search :contact-list)
:search-text search-text
:search-key :contact-list
:title (label :t/contacts)
:search-placeholder (label :t/search-contacts)}))
(defn actions-view [action click-handler]
[view actions-list
[action-button (label :t/enter-address)
:address_blue
#(do
(dispatch [:send-to-webview-bridge
{:event (name :webview-send-transaction)}])
(dispatch [:navigate-back]))]
[action-separator]
(if (= :request action)
[action-button (label :t/show-qr)
:q_r_blue
#(click-handler :qr-scan action)]
[action-button (label :t/scan-qr)
:fullscreen_blue
#(click-handler :qr-scan action)])])
(defn render-row [click-handler action params]
(fn [row _ _]
(list-item
^{:key row}
[contact-view {:contact row
:on-press #(when click-handler
(click-handler row action params))}])))
(defview contact-list-modal []
[contacts [:contacts-filtered :all-added-people-contacts]
click-handler [:get :contacts-click-handler]
action [:get :contacts-click-action]
params [:get :contacts-click-params]]
[drawer-view
[view {:flex 1}
[status-bar {:type :modal}]
[contact-list-modal-toolbar]
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow (render-row click-handler action params)
:bounces false
:keyboardShouldPersistTaps :always
:renderHeader #(list-item
[view
[actions-view action click-handler]
[common/bottom-shadow]
[common/form-title (label :t/choose-from-contacts)
{:count-value (count contacts)}]
[common/list-header]])
:renderFooter #(list-item [view
[common/list-footer]
[common/bottom-shadow]])
:renderSeparator renderers/list-separator-renderer
:style st/contacts-list-modal}]]])

View File

@ -1,119 +0,0 @@
(ns status-im.contacts.views.new-contact
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[clojure.string :as str]
[status-im.components.react :refer [view
text
image
touchable-highlight]]
[status-im.components.text-field.view :refer [text-field]]
[status-im.utils.identicon :refer [identicon]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar.view :refer [toolbar]]
[status-im.components.toolbar.actions :as act]
[status-im.components.toolbar.styles :refer [toolbar-title-container
toolbar-title-text
toolbar-background1]]
[status-im.utils.utils :refer [http-post]]
[status-im.components.styles :refer [icon-ok
button-input-container
button-input
color-blue]]
[status-im.components.image-button.view :refer [scan-button]]
[status-im.i18n :refer [label]]
[cljs.spec.alpha :as s]
[status-im.contacts.validations :as v]
[status-im.contacts.styles :as st]
[status-im.data-store.contacts :as contacts]
[status-im.utils.gfycat.core :refer [generate-gfy]]
[status-im.utils.hex :refer [normalize-hex]]
[status-im.utils.platform :refer [platform-specific]]))
(def toolbar-title
[view toolbar-title-container
[text {:style toolbar-title-text}
(label :t/add-new-contact)]])
(defn on-add-contact [id]
(if (v/is-address? id)
(http-post "get-contacts-by-address" {:addresses [id]}
(fn [{:keys [contacts]}]
(if (> (count contacts) 0)
(let [{:keys [whisper-identity]} (first contacts)
contact {:name (generate-gfy)
:address id
:photo-path (identicon whisper-identity)
:whisper-identity whisper-identity}]
(if (contacts/exists? whisper-identity)
(dispatch [:add-pending-contact whisper-identity])
(dispatch [:add-new-contact contact])))
(dispatch [:set :new-contact-public-key-error (label :t/unknown-address)]))))
(if (contacts/exists? id)
(dispatch [:add-pending-contact id])
(dispatch [:add-new-contact {:name (generate-gfy)
:photo-path (identicon id)
:whisper-identity id}]))))
(defn- validation-error-message
[whisper-identity {:keys [address public-key]} error]
(cond
(#{(normalize-hex address) (normalize-hex public-key)}
(normalize-hex whisper-identity))
(label :t/can-not-add-yourself)
(not (s/valid? ::v/whisper-identity whisper-identity))
(label :t/enter-valid-public-key)
(not (s/valid? ::v/contact-can-be-added whisper-identity))
(label :t/contact-already-added)
:else error))
(defn toolbar-actions [new-contact-identity account error]
(let [error-message (validation-error-message new-contact-identity account error)]
[{:image {:source {:uri (if (str/blank? error-message)
:icon_ok_blue
:icon_ok_disabled)}
:style icon-ok}
:handler #(when (str/blank? error-message)
(on-add-contact new-contact-identity))}]))
(defview contact-whisper-id-input [whisper-identity error]
[current-account [:get-current-account]]
(let [error (when-not (str/blank? whisper-identity)
(validation-error-message whisper-identity current-account error))]
[view button-input-container
[text-field
{:error error
:error-color color-blue
:input-style st/qr-input
:value whisper-identity
:wrapper-style button-input
:label (label :t/public-key)
:on-change-text #(do
(dispatch [:set-in [:new-contact-identity] %])
(dispatch [:set :new-contact-public-key-error nil]))}]
[scan-button {:show-label? (zero? (count whisper-identity))
:handler #(dispatch [:scan-qr-code
{:toolbar-title (label :t/new-contact)}
:set-contact-identity-from-qr])}]]))
(defview new-contact []
[new-contact-identity [:get :new-contact-identity]
error [:get :new-contact-public-key-error]
account [:get-current-account]]
[view st/contact-form-container
[status-bar]
[toolbar {:background-color toolbar-background1
:style (get-in platform-specific [:component-styles :toolbar])
:nav-action (act/back #(dispatch [:navigate-back]))
:title (label :t/add-new-contact)
:actions (toolbar-actions new-contact-identity account error)}]
[view st/form-container
[contact-whisper-id-input new-contact-identity error]]
[view st/address-explication-container
[text {:style st/address-explication
:font :default}
(label :t/address-explication)]]])

View File

@ -8,7 +8,7 @@
:keyboard-height 0
:accounts {}
:navigation-stack '()
:contacts {}
:contacts/contacts {}
:qr-codes {}
:contact-groups {}
:selected-contacts #{}

View File

@ -72,7 +72,7 @@
(register-handler :debug-add-contact
(u/side-effect!
(fn [{:keys [contacts]} [_ {:keys [name whisper-identity dapp-url bot-url] :as dapp-data}]]
(fn [{:contacts/keys [contacts]} [_ {:keys [name whisper-identity dapp-url bot-url] :as dapp-data}]]
(if (and name
whisper-identity
(or dapp-url bot-url))
@ -113,7 +113,8 @@
(register-handler :debug-contact-changed
(u/side-effect!
(fn [{:keys [webview-bridge current-chat-id contacts]} [_ {:keys [whisper-identity] :as dapp-data}]]
(fn [{:keys [webview-bridge current-chat-id]
:contacts/keys [contacts]} [_ {:keys [whisper-identity] :as dapp-data}]]
(when (get-in contacts [whisper-identity :debug?])
(when (and (= current-chat-id whisper-identity)
webview-bridge)
@ -132,7 +133,7 @@
(register-handler :debug-dapps-list
(u/side-effect!
(fn [{:keys [contacts]}]
(fn [{:contacts/keys [contacts]}]
(let [contacts (->> (vals contacts)
(filter :debug?)
(map #(select-keys % [:name :whisper-identity :dapp-url :bot-url])))]

View File

@ -29,7 +29,8 @@
(register-handler :broadcast-status
(u/side-effect!
(fn [{:keys [current-public-key web3 current-account-id accounts contacts]}
(fn [{:keys [current-public-key web3 current-account-id accounts]
:contacts/keys [contacts]}
[_ status hashtags]]
(let [{:keys [name photo-path]} (get accounts current-account-id)
message-id (random/id)
@ -73,7 +74,8 @@
(register-handler :request-discoveries
(u/side-effect!
(fn [{:keys [current-public-key web3 contacts]}]
(fn [{:keys [current-public-key web3]
:contacts/keys [contacts]}]
(doseq [id (u/identities contacts)]
(when-not (protocol/message-pending? web3 :discoveries-request id)
(protocol/send-discoveries-request!
@ -84,7 +86,8 @@
(register-handler :discoveries-send-portions
(u/side-effect!
(fn [{:keys [current-public-key contacts web3]} [_ to]]
(fn [{:keys [current-public-key web3]
:contacts/keys [contacts]} [_ to]]
(when (get contacts to)
(protocol/send-discoveries-response!
{:web3 web3
@ -99,7 +102,8 @@
(register-handler :discoveries-response-received
(u/side-effect!
(fn [{:keys [discoveries contacts]} [_ {:keys [payload from]}]]
(fn [{:keys [discoveries]
:contacts/keys [contacts]} [_ {:keys [payload from]}]]
(when (get contacts from)
(when-let [data (:data payload)]
(doseq [{:keys [message-id] :as discover} data]

View File

@ -76,7 +76,7 @@
(defview discover [current-view?]
[show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]
contacts [:get :contacts]
contacts [:get-contacts]
current-account [:get-current-account]
discoveries [:get-recent-discoveries]
tabs-hidden? [:tabs-hidden?]]

View File

@ -2,7 +2,8 @@
(:require [re-frame.core :refer [reg-sub]]
[status-im.utils.datetime :as time]))
(defn- calculate-priority [{:keys [chats contacts current-public-key]}
(defn- calculate-priority [{:keys [chats current-public-key]
:contacts/keys [contacts]}
{:keys [whisper-id created-at]}]
(let [contact (get contacts whisper-id)
chat (get chats whisper-id)

View File

@ -22,7 +22,7 @@
account-name :name
:as current-account} :current-account}]
[{contact-name :name
contact-photo-path :photo-path} [:get-in [:contacts whisper-id]]]
contact-photo-path :photo-path} [:get-in [:contacts/contacts whisper-id]]]
(let [item-style (get-in platform-specific [:component-styles :discover :item])]
[view
[view st/popular-list-item

View File

@ -5,7 +5,7 @@
(reg-sub :selected-participant
(fn [db]
(let [identity (first (:selected-participants db))]
(get-in db [:contacts identity]))))
(get-in db [:contacts/contacts identity]))))
(defn get-chat-name-validation-messages [chat-name]
(filter some?

View File

@ -1,17 +1,17 @@
(ns status-im.handlers
(:require
[re-frame.core :refer [after dispatch dispatch-sync debug]]
[re-frame.core :refer [after dispatch dispatch-sync debug reg-fx]]
[status-im.db :refer [app-db]]
[status-im.data-store.core :as data-store]
[taoensso.timbre :as log]
[status-im.utils.crypt :refer [gen-random-bytes]]
[status-im.components.status :as status]
[status-im.components.permissions :as permissions]
[status-im.utils.handlers :refer [register-handler] :as u]
[status-im.utils.handlers :refer [register-handler register-handler-fx] :as u]
status-im.chat.handlers
status-im.group-settings.handlers
status-im.navigation.handlers
status-im.contacts.handlers
status-im.contacts.events
status-im.discover.handlers
status-im.new-group.handlers
status-im.profile.handlers
@ -44,17 +44,23 @@
(register-handler :set-in set-in)
(register-handler :initialize-db
(fn [{:keys [status-module-initialized? status-node-started?
network-status network first-run]} _]
(data-store/init)
(assoc app-db :current-account-id nil
:contacts {}
:network-status network-status
:status-module-initialized? (or p/ios? js/goog.DEBUG status-module-initialized?)
:status-node-started? status-node-started?
:network (or network :testnet)
:first-run (or (nil? first-run) first-run))))
(reg-fx
::init-store
(fn []
(data-store/init)))
(register-handler-fx :initialize-db
(fn [{{:keys [status-module-initialized? status-node-started?
network-status network first-run _]} :db} _]
{::init-store nil
:db (assoc app-db
:current-account-id nil
:contacts/contacts {}
:network-status network-status
:status-module-initialized? (or p/ios? js/goog.DEBUG status-module-initialized?)
:status-node-started? status-node-started?
:network (or network :testnet)
:first-run (or (nil? first-run) first-run))}))
(register-handler :initialize-account-db
(fn [db _]
@ -62,7 +68,7 @@
(assoc :current-chat-id console-chat-id)
(dissoc :transactions
:transactions-queue
:new-contact-identity))))
:contacts/new-identity))))
(register-handler :initialize-account
(u/side-effect!

View File

@ -12,9 +12,9 @@
splash-screen
http-bridge]]
[status-im.components.main-tabs :refer [main-tabs]]
[status-im.contacts.views.contact-list :refer [contact-list]]
[status-im.contacts.views.contact-list-modal :refer [contact-list-modal]]
[status-im.contacts.views.new-contact :refer [new-contact]]
[status-im.contacts.contact-list.views :refer [contact-list]]
[status-im.contacts.contact-list-modal.views :refer [contact-list-modal]]
[status-im.contacts.new-contact.views :refer [new-contact]]
[status-im.qr-scanner.screen :refer [qr-scanner]]
[status-im.discover.search-results :refer [discover-search-results]]
[status-im.chat.screen :refer [chat]]

View File

@ -101,7 +101,7 @@
(defn show-profile
[db [_ identity]]
(dispatch [:navigate-forget :profile])
(assoc db :contact-identity identity))
(assoc db :contacts/identity identity))
(register-handler :show-profile show-profile)

View File

@ -53,7 +53,7 @@
(defview new-chat []
[contacts [:all-added-group-contacts-filtered]
params [:get :contacts-click-params]]
params [:get :contacts/click-params]]
[drawer-view
[view st/contacts-list-container
[new-chat-toolbar]

View File

@ -44,7 +44,8 @@
(register-handler :select-contact select-contact)
(defn group-name-from-contacts
[{:keys [contacts selected-contacts username]}]
[{:keys [selected-contacts username]
:contacts/keys [contacts]}]
(->> (select-keys contacts selected-contacts)
vals
(map :name)

View File

@ -13,7 +13,7 @@
(reg-sub :selected-contacts-count
:<- [:get :selected-contacts]
:<- [:get :contacts]
:<- [:get-contacts]
(fn [[selected-contacts contacts]]
;TODO temporary, contact should be deleted from group after contact deletion from contacts
(count (filter-selected-contacts selected-contacts contacts))))

View File

@ -17,7 +17,7 @@
(defview qr-code-view []
[{:keys [photo-path address name] :as contact} [:get-in [:qr-modal :contact]]
{:keys [qr-source amount? dimensions]} [:get :qr-modal]
{:keys [amount]} [:get :contacts-click-params]]
{:keys [amount]} [:get :contacts/click-params]]
[view st/wallet-qr-code
[status-bar {:type :modal}]
[view st/account-toolbar

View File

@ -396,7 +396,7 @@
(register-handler :contact-request-received
(u/side-effect!
(fn [{:keys [contacts]} [_ {:keys [from payload]}]]
(fn [{:contacts/keys [contacts]} [_ {:keys [from payload]}]]
(when from
(let [{{:keys [name profile-image address status]} :contact
{:keys [public private]} :keypair} payload

View File

@ -3,7 +3,7 @@
(:require [cljs.spec.alpha :as s]
[status-im.accounts.specs]
[status-im.navigation.specs]
[status-im.contacts.specs]
[status-im.contacts.db]
[status-im.qr-scanner.specs]
[status-im.new-group.specs]
[status-im.chat.specs]
@ -11,119 +11,134 @@
[status-im.transactions.specs]
[status-im.discover.specs]))
;GLOBAL
(s/def ::current-public-key (s/nilable string?)) ;;public key of current logged in account
(s/def ::first-run (s/nilable boolean?)) ;;true when application running at first time
;;;;GLOBAL
;;public key of current logged in account
(s/def ::current-public-key (s/nilable string?))
;;true when application running at first time
(s/def ::first-run (s/nilable boolean?))
(s/def ::was-modal? (s/nilable boolean?))
(s/def ::rpc-url (s/nilable string?)) ;;"http://localhost:8545"
(s/def ::web3 (s/nilable any?)) ;;object? doesn't work
(s/def ::webview-bridge (s/nilable any?)) ;;object?
;;"http://localhost:8545"
(s/def ::rpc-url (s/nilable string?))
;;object? doesn't work
(s/def ::web3 (s/nilable any?))
;;object?
(s/def ::webview-bridge (s/nilable any?))
(s/def ::status-module-initialized? (s/nilable boolean?))
(s/def ::status-node-started? (s/nilable boolean?))
(s/def ::toolbar-search (s/nilable map?))
(s/def ::keyboard-height (s/nilable number?)) ;;height of native keyboard if shown
;;height of native keyboard if shown
(s/def ::keyboard-height (s/nilable number?))
(s/def ::keyboard-max-height (s/nilable number?))
(s/def ::orientation (s/nilable keyword?)) ;;:unknown - not used
(s/def ::network-status (s/nilable keyword?)) ;;:online - presence of internet connection in the phone
;NODE
;;:unknown - not used
(s/def ::orientation (s/nilable keyword?))
;;:online - presence of internet connection in the phone
(s/def ::network-status (s/nilable keyword?))
;;;;NODE
(s/def ::sync-listening-started (s/nilable boolean?))
(s/def ::sync-state (s/nilable keyword?))
(s/def ::sync-data (s/nilable map?))
;NETWORK
(s/def ::network (s/nilable keyword?)) ;;network name :testnet
(s/def ::db (allowed-keys :opt-un
[::current-public-key
::first-run
::modal
::was-modal?
::rpc-url
::web3
::webview-bridge
::status-module-initialized?
::status-node-started?
::toolbar-search
::keyboard-height
::keyboard-max-height
::orientation
::network-status
::sync-listening-started
::sync-state
::sync-data
::network
:accounts/accounts
:accounts/account-creation?
:accounts/creating-account?
:accounts/current-account-id
:accounts/recover
:accounts/login
:navigation/view-id
:navigation/navigation-stack
:navigation/prev-tab-view-id
:navigation/prev-view-id
:contacts/contacts
:contacts/new-contacts
:contacts/new-contact-identity
:contacts/new-contact-public-key-error
:contacts/contact-identity
:contacts/contacts-ui-props
:contacts/contact-list-ui-props
:contacts/contacts-click-handler
:contacts/contacts-click-action
:contacts/contacts-click-params
:qr/qr-codes
:qr/qr-modal
:qr/current-qr-context
:group/contact-groups
:group/contact-group-id
:group/group-type
:group/new-group
:group/new-groups
:group/contacts-group
:group/selected-contacts
:group/groups-order
:chat/chats
:chat/current-chat-id
:chat/chat-id
:chat/new-chat
:chat/new-chat-name
:chat/chat-animations
:chat/chat-ui-props
:chat/chat-list-ui-props
:chat/layout-height
:chat/expandable-view-height-to-value
:chat/global-commands
:chat/loading-allowed
:chat/message-data
:chat/message-id->transaction-id
:chat/message-status
:chat/unviewed-messages
:chat/selected-participants
:chat/chat-loaded-callbacks
:chat/commands-callbacks
:chat/command-hash-valid?
:chat/public-group-topic
:chat/confirmation-code-sms-listener
:chat/messages
:chat/loaded-chats
:chat/bot-subscriptions
:chat/new-request
:chat/raw-unviewed-messages
:chat/bot-db
:chat/geolocation
:profile/profile-edit
:transactions/transactions
:transactions/transactions-queue
:transactions/selected-transaction
:transactions/confirm-transactions
:transactions/confirmed-transactions-count
:transactions/transactions-list-ui-props
:transactions/transaction-details-ui-props
:transactions/wrong-password-counter
:transactions/wrong-password?
:discoveries/discoveries
:discoveries/discover-search-tags
:discoveries/tags
:discoveries/current-tag
:discoveries/request-discoveries-timer
:discoveries/new-discover]))
;;;;NETWORK
;;network name :testnet
(s/def ::network (s/nilable keyword?))
(s/def ::db (allowed-keys
:opt
[:contacts/contacts
:contacts/new-identity
:contacts/new-public-key-error
:contacts/identity
:contacts/ui-props
:contacts/list-ui-props
:contacts/click-handler
:contacts/click-action
:contacts/click-params]
:opt-un
[::current-public-key
::first-run
::modal
::was-modal?
::rpc-url
::web3
::webview-bridge
::status-module-initialized?
::status-node-started?
::toolbar-search
::keyboard-height
::keyboard-max-height
::orientation
::network-status
::sync-listening-started
::sync-state
::sync-data
::network
:accounts/accounts
:accounts/account-creation?
:accounts/creating-account?
:accounts/current-account-id
:accounts/recover
:accounts/login
:navigation/view-id
:navigation/navigation-stack
:navigation/prev-tab-view-id
:navigation/prev-view-id
:qr/qr-codes
:qr/qr-modal
:qr/current-qr-context
:group/contact-groups
:group/contact-group-id
:group/group-type
:group/new-group
:group/new-groups
:group/contacts-group
:group/selected-contacts
:group/groups-order
:chat/chats
:chat/current-chat-id
:chat/chat-id
:chat/new-chat
:chat/new-chat-name
:chat/chat-animations
:chat/chat-ui-props
:chat/chat-list-ui-props
:chat/layout-height
:chat/expandable-view-height-to-value
:chat/global-commands
:chat/loading-allowed
:chat/message-data
:chat/message-id->transaction-id
:chat/message-status
:chat/unviewed-messages
:chat/selected-participants
:chat/chat-loaded-callbacks
:chat/commands-callbacks
:chat/command-hash-valid?
:chat/public-group-topic
:chat/confirmation-code-sms-listener
:chat/messages
:chat/loaded-chats
:chat/bot-subscriptions
:chat/new-request
:chat/raw-unviewed-messages
:chat/bot-db
:chat/geolocation
:profile/profile-edit
:transactions/transactions
:transactions/transactions-queue
:transactions/selected-transaction
:transactions/confirm-transactions
:transactions/confirmed-transactions-count
:transactions/transactions-list-ui-props
:transactions/transaction-details-ui-props
:transactions/wrong-password-counter
:transactions/wrong-password?
:discoveries/discoveries
:discoveries/discover-search-tags
:discoveries/tags
:discoveries/current-tag
:discoveries/request-discoveries-timer
:discoveries/new-discover]))

View File

@ -11,7 +11,7 @@
(reg-sub :get
(fn [db [_ k]]
(k db)))
(get db k)))
(reg-sub :get-current-account
(fn [db]
@ -32,7 +32,7 @@
(reg-sub :tabs-hidden?
:<- [:get-in [:toolbar-search :show]]
:<- [:get-in [:chat-list-ui-props :edit?]]
:<- [:get-in [:contacts-ui-props :edit?]]
:<- [:get-in [:contacts/ui-props :edit?]]
:<- [:get :view-id]
(fn [[search-mode? chats-edit-mode? contacts-edit-mode? view-id]]
(or search-mode?

View File

@ -11,7 +11,7 @@
(into {} (map (fn [[_ {:keys [address] :as contact}]]
(when address
[address contact]))
(:contacts db)))))
(:contacts/contacts db)))))
(reg-sub :contact-by-address
:<- [:contacts-by-address]

View File

@ -1,5 +1,5 @@
(ns status-im.utils.handlers
(:require [re-frame.core :refer [reg-event-db]]
(:require [re-frame.core :refer [reg-event-db reg-event-fx]]
[re-frame.interceptor :refer [->interceptor get-coeffect]]
[clojure.string :as str]
[taoensso.timbre :as log]
@ -26,7 +26,7 @@
"throw an exception if db doesn't match the spec"
(->interceptor
:id check-spec
:before
:after
(fn check-handler
[context]
(let [new-db (get-coeffect context :db)
@ -40,6 +40,16 @@
([name middleware handler]
(reg-event-db name [debug-handlers-names (when js/goog.DEBUG check-spec) middleware] handler)))
(defn register-handler-db
([name handler] (register-handler-db name nil handler))
([name interceptors handler]
(reg-event-db name [debug-handlers-names (when js/goog.DEBUG check-spec) interceptors] handler)))
(defn register-handler-fx
([name handler] (register-handler-fx name nil handler))
([name interceptors handler]
(reg-event-fx name [debug-handlers-names (when js/goog.DEBUG check-spec) interceptors] handler)))
(defn get-hashtags [status]
(if status
(let [hashtags (map #(str/lower-case (subs % 1))

View File

@ -1,14 +0,0 @@
(ns reagent.core)
(defn create-element [& args])
(defn adapt-react-class [& args])
(defn atom [& args])
(defn as-element [& args])
(defn create-class [& args])
(defn set-state [& args])
(defn state [& args])
(defn after-render [& args])
(defn props [& args])
(defn current-component [& args])
(defn reactify-component [& args])

View File

@ -2,14 +2,14 @@
(def action-button #js {})
(def android-sms-listener #js {})
(def autolink #js {})
(def autolink #js {:default #js {}})
(def camera #js {:constants #js {}})
(def circle-checkbox #js {})
(def contacts #js {})
(def dialogs #js {})
(def dismiss-keyboard #js {})
(def drawer #js {})
(def emoji-picker #js {})
(def emoji-picker #js {:default #js {}})
(def fs #js {})
(def http-bridge #js {})
(def i18n #js {})
@ -26,13 +26,14 @@
(def random-bytes #js {})
(def react-native
#js {:NativeModules #js {}
:Animated #js {}
:Animated #js {:View #js {}
:Text #js {}}
:DeviceEventEmitter #js {:addListener (fn [])}})
(def realm #js {:schemaVersion (fn [])
:close (fn [])})
(def sortable-listview #js {})
(def swiper #js {})
(def vector-icons #js {})
(def webview-bridge #js {})
(def webview-bridge #js {:default #js {}})

View File

@ -0,0 +1,193 @@
(ns status-im.test.contacts.handlers
(:require [cljs.test :refer-macros [deftest is]]
reagent.core
[re-frame.core :as rf]
[day8.re-frame.test :refer-macros [run-test-sync]]
status-im.specs
status-im.db
[status-im.contacts.events :as e]
[status-im.handlers :as h]
status-im.subs))
(def browse-contact-from-realm-db
{:last-updated 0
:address nil
:name "Browse"
:global-command
{
:description "Launch the browser"
:sequential-params false
:color "#ffa500"
:name "global"
:params
{
:0
{
:name "url"
:type "text"
:placeholder "URL"}}
:icon nil
:title "Browser"
:has-handler false
:fullscreen true
:suggestions-trigger "on-change"}
:dapp-url nil
:dapp-hash nil
:commands
{
:location
{
:description "Share your location"
:sequential-params true
:color nil
:name "location"
:params
{
:0
{
:name "address"
:type "text"
:placeholder "address"}}
:icon nil
:title "Location"
:has-handler false
:fullscreen true
:owner-id "browse"
:suggestions-trigger "on-change"}}
:photo-path nil
:debug? false
:status nil
:bot-url "local://browse-bot"
:responses {}
:pending? false
:whisper-identity "browse"
:last-online 0
:dapp? true
:unremovable? true
:private-key nil
:public-key nil})
(def browse-global-commands
{:browse
{
:description "Launch the browser"
:bot "browse"
:color "#ffa500"
:name "global"
:params
[
{
:name "url"
:placeholder "URL"
:type "text"}]
:type :command
:title "Browser"
:sequential-params false
:icon nil
:has-handler false
:fullscreen true
:suggestions-trigger "on-change"}})
#_{:registered-only true
:has-handler false
:fullscreen true
:hidden? nil}
(def browse-contatcs
{
"browse"
{
:last-updated 0
:address nil
:name "Browse"
:global-command
{
:description "Launch the browser"
:sequential-params false
:color "#ffa500"
:name "global"
:params
{
:0
{
:name "url"
:type "text"
:placeholder "URL"}}
:icon nil
:title "Browser"
:has-handler false
:fullscreen true
:suggestions-trigger "on-change"}
:commands-loaded? true
:dapp-url nil
:dapp-hash nil
:subscriptions {}
:commands
{
:location
{
:description "Share your location"
:bot "mailman"
:sequential-params true
:hide-send-button true
:name "location"
:params
[
{
:name "address"
:placeholder "address"
:type "text"}]
:type :command
:title "Location"
:has-handler false
:hidden? nil
:owner-id "mailman"}}}
:photo-path nil
:debug? false
:status nil
:bot-url "local://browse-bot"
:responses {}
:pending? false
:whisper-identity "browse"
:last-online 0
:dapp? true
:unremovable? true
:private-key nil
:public-key nil})
(defn test-fixtures
[]
(rf/reg-cofx
::e/get-all-contacts
(fn [coeffects _]
(assoc coeffects :all-contacts [browse-contact-from-realm-db])))
(rf/reg-fx
::h/init-store
(fn []
nil)))
(deftest basic-sync
(run-test-sync
(test-fixtures)
(rf/dispatch [:initialize-db])
(let [contacts (rf/subscribe [:get-contacts])
global-commands (rf/subscribe [:get :global-commands])]
;;Assert the initial state
(is (and (map? @contacts) (empty? @contacts)))
(is (nil? @global-commands))
(rf/dispatch [:load-contacts])
(is (= {"browse" browse-contact-from-realm-db} @contacts))
(is (= browse-global-commands @global-commands)))))

View File

@ -1,25 +0,0 @@
(ns status-im.test.handlers-stubs
(:require [re-frame.core :refer [subscribe dispatch after]]
[status-im.utils.handlers :refer [register-handler]]
[status-im.utils.handlers :as u]
[status-im.chat.sign-up :as sign-up-service]
status-im.handlers))
(defn init-stubs []
(register-handler :sign-up
(u/side-effect!
(fn []
;; todo save phone number to db
(sign-up-service/on-sign-up-response))))
(register-handler :sign-up-confirm
(u/side-effect!
(fn [_ [_ confirmation-code]]
(sign-up-service/on-send-code-response
(if (= "1234" confirmation-code)
{:message "Done!"
:status :confirmed
:confirmed true}
{:message "Wrong code!"
:status :failed
:confirmed false}))))))

View File

@ -1,5 +1,6 @@
(ns status-im.test.runner
(:require [doo.runner :refer-macros [doo-tests]]
[status-im.test.contacts.handlers]
[status-im.test.chat.models.input]
[status-im.test.handlers]
[status-im.test.utils.utils]))
@ -12,6 +13,7 @@
(set! goog.DEBUG false)
(doo-tests 'status-im.test.chat.models.input
(doo-tests 'status-im.test.contacts.handlers
'status-im.test.chat.models.input
'status-im.test.handlers
'status-im.test.utils.utils)