Allow scanning qr codes for custom bootnodes

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2018-06-01 12:08:16 +02:00
parent b345b4a79a
commit 9058c54933
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
8 changed files with 244 additions and 90 deletions

View File

@ -0,0 +1,63 @@
(ns status-im.models.bootnode
(:require
[clojure.string :as string]
[status-im.ui.screens.accounts.utils :as accounts.utils]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.utils.ethereum.core :as ethereum]))
(def address-regex #"enode://[a-zA-Z0-9]+:?(.*)\@\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b:(\d{1,5})")
(defn valid-address? [address]
(re-matches address-regex address))
(defn- build [id bootnode-name address chain]
{:address address
:chain chain
:id (string/replace id "-" "")
:name bootnode-name})
(defn fetch [id cofx]
(let [network (get-in cofx [:db :network])]
(get-in cofx [:db :account/account :bootnodes network id])))
(defn set-input [input-key value {:keys [db]}]
{:db (update
db
:bootnodes/manage
assoc
input-key
{:value value
:error (case input-key
:id false
:name (string/blank? value)
:url (not (valid-address? value)))})})
(defn edit [id {:keys [db] :as cofx}]
(let [{:keys [id
address
name]} (fetch id cofx)
fxs (handlers-macro/merge-fx
cofx
(set-input :id id)
(set-input :url (str address))
(set-input :name (str name)))]
(assoc fxs :dispatch [:navigate-to :edit-bootnode])))
(defn save [{{:bootnodes/keys [manage] :account/keys [account] :as db} :db :as cofx}]
(let [{:keys [name url]} manage
network (:network db)
bootnode (build
(:random-id cofx)
(:value name)
(:value url)
network)
new-bootnodes (assoc-in
(:bootnodes account)
[network (:id bootnode)]
bootnode)]
(handlers-macro/merge-fx
cofx
{:db (dissoc db :bootnodes/manage)
:dispatch [:navigate-back]}
(accounts.utils/account-update {:bootnodes new-bootnodes}))))

View File

@ -2,50 +2,26 @@
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.utils.handlers :refer [register-handler] :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.ui.screens.accounts.utils :as accounts.utils]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.types :as types]
[status-im.utils.inbox :as utils.inbox]))
(defn- new-bootnode [id bootnode-name address chain]
{:address address
:chain chain
:id (string/replace id "-" "")
:name bootnode-name})
(defn save-new-bootnode [{{:bootnodes/keys [manage] :account/keys [account] :as db} :db :as cofx} _]
(let [{:keys [name url]} manage
network (:network db)
bootnode (new-bootnode
(:random-id cofx)
(:value name)
(:value url)
network)
new-bootnodes (assoc-in (:bootnodes account) [network (:id bootnode)] bootnode)]
(handlers-macro/merge-fx cofx
{:db (dissoc db :bootnodes/manage)
:dispatch [:navigate-back]}
(accounts.utils/account-update {:bootnodes new-bootnodes}))))
[status-im.models.bootnode :as models.bootnode]))
(handlers/register-handler-fx
:save-new-bootnode
[(re-frame/inject-cofx :random-id)]
save-new-bootnode)
(fn [cofx _]
(models.bootnode/save cofx)))
(handlers/register-handler-fx
:bootnode-set-input
(fn [{db :db} [_ input-key value]]
{:db (update db :bootnodes/manage assoc input-key {:value value
:error (if (= input-key :name)
(string/blank? value)
(not (utils.inbox/valid-enode-address? value)))})}))
(fn [cofx [_ input-key value]]
(models.bootnode/set-input input-key value cofx)))
(handlers/register-handler-fx
:edit-bootnode
(fn [{db :db} _]
{:db (update-in db [:bootnodes/manage] assoc
:name {:error true}
:url {:error true})
:dispatch [:navigate-to :edit-bootnode]}))
(fn [cofx [_ bootnode-id]]
(models.bootnode/edit bootnode-id cofx)))
(handlers/register-handler-fx
:set-bootnode-from-qr
(fn [cofx [_ _ url]]
(assoc (models.bootnode/set-input :url url cofx)
:dispatch [:navigate-back])))

View File

@ -1,14 +1,30 @@
(ns status-im.ui.screens.bootnodes-settings.edit-bootnode.styles
(:require-macros [status-im.utils.styles :refer [defstyle]]))
(:require-macros [status-im.utils.styles :refer [defstyle]])
(:require [status-im.ui.components.styles :as styles]
[status-im.ui.components.colors :as colors]))
(def input-container
{:flex-direction :row
:align-items :center
:justify-content :space-between
:border-radius styles/border-radius
:height 52
:margin-top 15})
(defstyle input
{:flex 1
:font-size 15
:letter-spacing -0.2
:android {:padding 0}})
(def qr-code
{:margin-right 14})
(def edit-bootnode-view
{:flex 1
:margin-horizontal 16
:margin-vertical 15})
(def input-container
{:margin-bottom 15})
(def bottom-container
{:flex-direction :row
:margin-horizontal 12

View File

@ -6,37 +6,53 @@
[status-im.i18n :as i18n]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.common.common :as components.common]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.text-input.view :as text-input]
[status-im.ui.screens.bootnodes-settings.edit-bootnode.styles :as styles]))
(def qr-code
[react/touchable-highlight {:on-press #(re-frame/dispatch [:scan-qr-code
{:toolbar-title (i18n/label :t/add-bootnode)}
:set-bootnode-from-qr])
:style styles/qr-code}
[react/view
[vector-icons/icon :icons/qr {:color colors/blue}]]])
(views/defview edit-bootnode []
(views/letsubs [manage-bootnode [:get-manage-bootnode]
is-valid? [:manage-bootnode-valid?]]
[react/view components.styles/flex
[status-bar/status-bar]
[react/keyboard-avoiding-view components.styles/flex
[toolbar/simple-toolbar (i18n/label :t/add-bootnode)]
[react/scroll-view
[react/view styles/edit-bootnode-view
[text-input/text-input-with-label
{:label (i18n/label :t/name)
:placeholder (i18n/label :t/specify-name)
:container styles/input-container
:default-value (get-in manage-bootnode [:name :value])
:on-change-text #(re-frame/dispatch [:bootnode-set-input :name %])
:auto-focus true}]
[text-input/text-input-with-label
{:label (i18n/label :t/bootnode-address)
:placeholder (i18n/label :t/specify-bootnode-address)
:container styles/input-container
:default-value (get-in manage-bootnode [:url :value])
:on-change-text #(re-frame/dispatch [:bootnode-set-input :url %])}]]]
[react/view styles/bottom-container
[react/view components.styles/flex]
[components.common/bottom-button
{:forward? true
:label (i18n/label :t/save)
:disabled? (not is-valid?)
:on-press #(re-frame/dispatch [:save-new-bootnode])}]]]]))
(let [url (get-in manage-bootnode [:url :value])
name (get-in manage-bootnode [:name :value])]
[react/view components.styles/flex
[status-bar/status-bar]
[react/keyboard-avoiding-view components.styles/flex
[toolbar/simple-toolbar (i18n/label :t/add-bootnode)]
[react/scroll-view
[react/view styles/edit-bootnode-view
[text-input/text-input-with-label
{:label (i18n/label :t/name)
:placeholder (i18n/label :t/specify-name)
:style styles/input
:container styles/input-container
:default-value name
:on-change-text #(re-frame/dispatch [:bootnode-set-input :name %])
:auto-focus true}]
[text-input/text-input-with-label
{:label (i18n/label :t/bootnode-address)
:placeholder (i18n/label :t/specify-bootnode-address)
:content qr-code
:style styles/input
:container styles/input-container
:default-value url
:on-change-text #(re-frame/dispatch [:bootnode-set-input :url %])}]]]
[react/view styles/bottom-container
[react/view components.styles/flex]
[components.common/bottom-button
{:forward? true
:label (i18n/label :t/save)
:disabled? (not is-valid?)
:on-press #(re-frame/dispatch [:save-new-bootnode])}]]]])))

View File

@ -27,6 +27,6 @@
(handlers/register-handler-fx
:set-mailserver-from-qr
(fn [cofx [_ _ contact-identity]]
(assoc (models.mailserver/set-input :url contact-identity cofx)
(fn [cofx [_ _ url]]
(assoc (models.mailserver/set-input :url url cofx)
:dispatch [:navigate-back])))

View File

@ -0,0 +1,102 @@
(ns status-im.test.models.bootnode
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.models.bootnode :as model]))
(def bootnode-id "1da276e34126e93babf24ec88aac1a7602b4cbb2e11b0961d0ab5e989ca9c261aa7f7c1c85f15550a5f1e5a5ca2305b53b9280cf5894d5ecf7d257b173136d40")
(def host "167.99.209.61:30504")
(def valid-bootnode-address (str "enode://" bootnode-id "@" host))
(deftest save-bootnode
(testing "adding a bootnode"
(let [new-bootnode {:name {:value "name"}
:url {:value "url"}}
expected {"mainnet_rpc" {"someid" {:name "name"
:address "url"
:chain "mainnet_rpc"
:id "someid"}}}
actual (model/save
{:random-id "some-id"
:db {:bootnodes/manage new-bootnode
:network "mainnet_rpc"
:account/account {}}})]
(is (= expected (get-in actual [:db :account/account :bootnodes]))))))
(deftest set-input-bootnode
(testing "it validates names"
(testing "correct name"
(is (= {:db {:bootnodes/manage {:name {:value "value"
:error false}}}}
(model/set-input :name "value" {}))))
(testing "blank name"
(is (= {:db {:bootnodes/manage {:name {:value ""
:error true}}}}
(model/set-input :name "" {})))))
(testing "it validates bootnode url"
(testing "correct url"
(is (= {:db {:bootnodes/manage {:url {:value valid-bootnode-address
:error false}}}}
(model/set-input :url valid-bootnode-address {}))))
(testing "broken url"
(is (= {:db {:bootnodes/manage {:url {:value "broken"
:error true}}}}
(model/set-input :url "broken" {}))))))
(deftest edit-bootnode
(let [db {:network "mainnet_rpc"
:account/account
{:bootnodes
{"mainnet_rpc"
{"a" {:id "a"
:name "name"
:address valid-bootnode-address}}}}}
cofx {:db db}]
(testing "when no id is given"
(let [actual (model/edit nil cofx)]
(testing "it resets :bootnodes/manage"
(is (= {:id {:value nil
:error false}
:url {:value ""
:error true}
:name {:value ""
:error true}}
(-> actual :db :bootnodes/manage))))
(testing "it navigates to edit-bootnode view"
(is (= [:navigate-to :edit-bootnode]
(-> actual :dispatch))))))
(testing "when an id is given"
(testing "when the bootnode is in the list"
(let [actual (model/edit "a" cofx)]
(testing "it populates the fields with the correct values"
(is (= {:id {:value "a"
:error false}
:url {:value valid-bootnode-address
:error false}
:name {:value "name"
:error false}}
(-> actual :db :bootnodes/manage))))
(testing "it navigates to edit-bootnode view"
(is (= [:navigate-to :edit-bootnode]
(-> actual :dispatch))))))
(testing "when the bootnode is not in the list"
(let [actual (model/edit "not-existing" cofx)]
(testing "it populates the fields with the correct values"
(is (= {:id {:value nil
:error false}
:url {:value ""
:error true}
:name {:value ""
:error true}}
(-> actual :db :bootnodes/manage))))
(testing "it navigates to edit-bootnode view"
(is (= [:navigate-to :edit-bootnode]
(-> actual :dispatch)))))))))
(deftest fetch-bootnode
(testing "it fetches the bootnode from the db"
(let [cofx {:db {:network "mainnet_rpc"
:account/account {:bootnodes {"mainnet_rpc"
{"a" {:id "a"
:name "name"
:address "enode://old-id:old-password@url:port"}}}}}}]
(is (model/fetch "a" cofx)))))

View File

@ -11,6 +11,7 @@
[status-im.test.profile.events]
[status-im.test.bots.events]
[status-im.test.models.mailserver]
[status-im.test.models.bootnode]
[status-im.test.transport.core]
[status-im.test.chat.models]
[status-im.test.chat.models.input]
@ -36,7 +37,6 @@
[status-im.test.utils.mixpanel]
[status-im.test.utils.prices]
[status-im.test.ui.screens.network-settings.edit-network.events]
[status-im.test.ui.screens.bootnodes-settings.edit-bootnode.events]
[status-im.test.ui.screens.accounts.login.events]))
(enable-console-print!)
@ -58,6 +58,7 @@
'status-im.test.profile.events
'status-im.test.data-store.realm.core
'status-im.test.models.mailserver
'status-im.test.models.bootnode
'status-im.test.bots.events
'status-im.test.transport.core
'status-im.test.wallet.subs
@ -84,5 +85,4 @@
'status-im.test.utils.mixpanel
'status-im.test.utils.prices
'status-im.test.ui.screens.network-settings.edit-network.events
'status-im.test.ui.screens.bootnodes-settings.edit-bootnode.events
'status-im.test.ui.screens.accounts.login.events)

View File

@ -1,19 +0,0 @@
(ns status-im.test.ui.screens.bootnodes-settings.edit-bootnode.events
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.ui.screens.bootnodes-settings.edit-bootnode.events :as events]))
(deftest add-new-bootnode
(testing "adding a bootnode"
(let [new-bootnode {:name {:value "name"}
:url {:value "url"}}
expected {"mainnet_rpc" {"someid" {:name "name"
:address "url"
:chain "mainnet_rpc"
:id "someid"}}}
actual (events/save-new-bootnode
{:random-id "some-id"
:db {:bootnodes/manage new-bootnode
:network "mainnet_rpc"
:account/account {}}}
nil)]
(is (= expected (get-in actual [:db :account/account :bootnodes]))))))