diff --git a/resources/images/icons2/16x16/alert@2x.png b/resources/images/icons2/16x16/alert@2x.png new file mode 100644 index 0000000000..6c968f3061 Binary files /dev/null and b/resources/images/icons2/16x16/alert@2x.png differ diff --git a/resources/images/icons2/16x16/alert@3x.png b/resources/images/icons2/16x16/alert@3x.png new file mode 100644 index 0000000000..57bc93f0e2 Binary files /dev/null and b/resources/images/icons2/16x16/alert@3x.png differ diff --git a/resources/images/ui/add-contact@2x.png b/resources/images/ui/add-contact@2x.png new file mode 100644 index 0000000000..83d154da7e Binary files /dev/null and b/resources/images/ui/add-contact@2x.png differ diff --git a/resources/images/ui/add-contact@3x.png b/resources/images/ui/add-contact@3x.png new file mode 100644 index 0000000000..2b01a043ce Binary files /dev/null and b/resources/images/ui/add-contact@3x.png differ diff --git a/src/status_im/add_new/db.cljs b/src/status_im/add_new/db.cljs index 863ad45e1f..455ce01e5f 100644 --- a/src/status_im/add_new/db.cljs +++ b/src/status_im/add_new/db.cljs @@ -1,7 +1,7 @@ (ns status-im.add-new.db (:require [cljs.spec.alpha :as spec] [status-im.ethereum.ens :as ens] - [status-im.utils.db :as utils.db])) + [status-im2.utils.validators :as validators])) (defn own-public-key? [{:keys [multiaccount]} public-key] @@ -10,7 +10,7 @@ (defn validate-pub-key [db public-key] (cond - (or (not (utils.db/valid-public-key? public-key)) + (or (not (validators/valid-public-key? public-key)) (= public-key ens/default-key)) :invalid (own-public-key? db public-key) @@ -27,4 +27,4 @@ [topic] (and topic (spec/valid? ::topic topic) - (not (utils.db/valid-public-key? topic)))) \ No newline at end of file + (not (validators/valid-public-key? topic)))) diff --git a/src/status_im/ethereum/ens.cljs b/src/status_im/ethereum/ens.cljs index a6db4d0a2d..978556f0f6 100644 --- a/src/status_im/ethereum/ens.cljs +++ b/src/status_im/ethereum/ens.cljs @@ -35,15 +35,16 @@ :on-error #(cb "0x")})) (defn pubkey - [chain-id ens-name cb] - {:pre [(is-valid-eth-name? ens-name)]} - (json-rpc/call {:method "ens_publicKeyOf" - :params [chain-id ens-name] - :on-success cb - ;;at some point infura started to return execution reverted error instead of "0x" - ;;result - ;;our code expects "0x" result - :on-error #(cb "0x")})) + ([chain-id ens-name on-success on-error] + {:pre [(is-valid-eth-name? ens-name)]} + (json-rpc/call {:method "ens_publicKeyOf" + :params [chain-id ens-name] + :on-success on-success + :on-error on-error})) + ;; At some point, infura started to return "execution reverted" error + ;; instead of "0x" result. Our code expects "0x" result. + ([chain-id ens-name cb] + (pubkey chain-id ens-name cb #(cb "0x")))) (defn owner [chain-id ens-name cb] diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index f065b82ab0..9d120169d9 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -2,7 +2,6 @@ (:require clojure.set [re-frame.core :as re-frame] - status-im.add-new.core [status-im.async-storage.core :as async-storage] status-im.backup.core status-im.bootnodes.core diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index 0af2b03e31..c24adc3f14 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -1,7 +1,7 @@ (ns status-im.native-module.core (:require ["react-native" :as react-native] [re-frame.core :as re-frame] - [status-im.utils.db :as utils.db] + [status-im2.utils.validators :as validators] [status-im.utils.platform :as platform] [status-im.utils.react-native :as react-native-utils] [status-im.utils.types :as types] @@ -472,13 +472,13 @@ "Generate a 3 words random name based on the user public-key, synchronously" [public-key] (log/debug "[native-module] generate-gfycat") - (when (utils.db/valid-public-key? public-key) + (when (validators/valid-public-key? public-key) (.generateAlias ^js (status) public-key))) (defn generate-gfycat-async "Generate a 3 words random name based on the user public-key, asynchronously" [public-key callback] - (when (utils.db/valid-public-key? public-key) + (when (validators/valid-public-key? public-key) (.generateAliasAsync ^js (status) public-key callback))) (defn identicon diff --git a/src/status_im/react_native/resources.cljs b/src/status_im/react_native/resources.cljs index 2e95848f8d..2c865e931e 100644 --- a/src/status_im/react_native/resources.cljs +++ b/src/status_im/react_native/resources.cljs @@ -5,6 +5,7 @@ {:empty-chats-header (js/require "../resources/images/ui/empty-chats-header.png") :welcome (js/require "../resources/images/ui/welcome.jpg") :welcome-dark (js/require "../resources/images/ui/welcome-dark.jpg") + :add-new-contact (js/require "../resources/images/ui/add-contact.png") :chat (js/require "../resources/images/ui/chat.jpg") :chat-dark (js/require "../resources/images/ui/chat-dark.jpg") :wallet (js/require "../resources/images/ui/wallet.jpg") diff --git a/src/status_im/router/core.cljs b/src/status_im/router/core.cljs index 67aba5927c..4f134b190a 100644 --- a/src/status_im/router/core.cljs +++ b/src/status_im/router/core.cljs @@ -9,7 +9,7 @@ [status-im.ethereum.eip681 :as eip681] [status-im.ethereum.ens :as ens] [status-im.ethereum.stateofus :as stateofus] - [status-im.utils.db :as utils.db] + [status-im2.utils.validators :as validators] [status-im.utils.http :as http] [status-im.utils.wallet-connect :as wallet-connect] [taoensso.timbre :as log] @@ -71,7 +71,7 @@ (defn match-contact-async [chain {:keys [user-id ens-name]} callback] - (let [valid-key (and (utils.db/valid-public-key? user-id) + (let [valid-key (and (validators/valid-public-key? user-id) (not= user-id ens/default-key))] (cond valid-key @@ -113,8 +113,8 @@ (not (string/blank? chat-name)) (> (count chat-id-parts) 1) (not (string/blank? (first chat-id-parts))) - (utils.db/valid-public-key? admin-pk) - (utils.db/valid-public-key? (last chat-id-parts))) + (validators/valid-public-key? admin-pk) + (validators/valid-public-key? (last chat-id-parts))) {:type :group-chat :chat-id chat-id :invitation-admin admin-pk @@ -228,7 +228,7 @@ (= handler :group-chat) (cb (match-group-chat chats query-params)) - (utils.db/valid-public-key? uri) + (validators/valid-public-key? uri) (match-contact-async chain {:user-id uri} cb) (= handler :community-requests) diff --git a/src/status_im/ui/screens/add_new/new_chat/views.cljs b/src/status_im/ui/screens/add_new/new_chat/views.cljs index 8c3f92f1a0..9fc095fcee 100644 --- a/src/status_im/ui/screens/add_new/new_chat/views.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/views.cljs @@ -20,7 +20,7 @@ [status-im.ui.components.react :as react] [status-im.ui.components.topbar :as topbar] [status-im.ui.screens.chat.photos :as photos] - [status-im.utils.db :as utils.db] + [status-im2.utils.validators :as validators] [status-im.utils.gfycat.core :as gfycat] [status-im.utils.identicon :as identicon] [status-im.utils.utils :as utils] @@ -172,7 +172,7 @@ #(do (reset! search-value %) (re-frame/dispatch [:set-in [:contacts/new-identity :state] :empty]) - (debounce/debounce-and-dispatch [:new-chat/set-new-identity %] 600)) + (debounce/debounce-and-dispatch [:contacts/set-new-identity %] 600)) :on-submit-editing #(when (= state :valid) (debounce/dispatch-and-chill [:contact.ui/contact-code-submitted false nil] 3000)) @@ -301,7 +301,7 @@ (fn [] (let [{:keys [state ens-name public-key error]} @(re-frame/subscribe [:contacts/new-identity]) blocked? (and - (utils.db/valid-public-key? (or public-key "")) + (validators/valid-public-key? (or public-key "")) @(re-frame/subscribe [:contacts/contact-blocked? public-key]))] [react/view {:style {:flex 1}} @@ -326,7 +326,7 @@ {:on-change-text #(do (re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching]) - (debounce/debounce-and-dispatch [:new-chat/set-new-identity %] 600)) + (debounce/debounce-and-dispatch [:contacts/set-new-identity %] 600)) :on-submit-editing #(when (= state :valid) (debounce/dispatch-and-chill [:contact.ui/contact-code-submitted true @entered-nickname] diff --git a/src/status_im/ui/screens/home/views.cljs b/src/status_im/ui/screens/home/views.cljs index 3648d6a8b5..2904bb9838 100644 --- a/src/status_im/ui/screens/home/views.cljs +++ b/src/status_im/ui/screens/home/views.cljs @@ -7,7 +7,6 @@ [quo2.foundations.colors :as quo2.colors] [re-frame.core :as re-frame] [reagent.core :as reagent] - [status-im.add-new.core :as new-chat] [status-im.add-new.db :as db] [utils.i18n :as i18n] [status-im.qr-scanner.core :as qr-scanner] @@ -82,15 +81,15 @@ :on-blur (fn [] (when chats-empty (re-frame/dispatch [:search/home-filter-changed nil])) - (re-frame/dispatch [::new-chat/clear-new-identity])) + (re-frame/dispatch [:contacts/clear-new-identity])) :on-focus (fn [search-filter] (when-not search-filter (re-frame/dispatch [:search/home-filter-changed ""]) - (re-frame/dispatch [::new-chat/clear-new-identity]))) + (re-frame/dispatch [:contacts/clear-new-identity]))) :on-change (fn [text] (re-frame/dispatch [:search/home-filter-changed text]) (re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching]) - (debounce/debounce-and-dispatch [:new-chat/set-new-identity text] 300))}]]) + (debounce/debounce-and-dispatch [:contacts/set-new-identity text] 300))}]]) (defn search-input-wrapper-old [search-filter chats-empty] @@ -104,11 +103,11 @@ :on-blur (fn [] (when chats-empty (re-frame/dispatch [:search/home-filter-changed nil])) - (re-frame/dispatch [::new-chat/clear-new-identity])) + (re-frame/dispatch [:contacts/clear-new-identity])) :on-focus (fn [search-filter] (when-not search-filter (re-frame/dispatch [:search/home-filter-changed ""]) - (re-frame/dispatch [::new-chat/clear-new-identity]))) + (re-frame/dispatch [:contacts/clear-new-identity]))) :on-change (fn [text] (re-frame/dispatch [:search/home-filter-changed text]) (re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching]) diff --git a/src/status_im/ui/screens/screens.cljs b/src/status_im/ui/screens/screens.cljs index 416e4b2a47..712f69723a 100644 --- a/src/status_im/ui/screens/screens.cljs +++ b/src/status_im/ui/screens/screens.cljs @@ -1,7 +1,6 @@ (ns status-im.ui.screens.screens (:require [quo.design-system.colors :as colors] - [status-im.add-new.core :as new-chat.events] [utils.i18n :as i18n] [status-im.ui.components.icons.icons :as icons] [status-im.ui.screens.about-app.views :as about-app] @@ -545,7 +544,7 @@ ;[Chat] New Chat {:name :new-chat - :on-focus [::new-chat.events/new-chat-focus] + :on-focus [:contacts/new-chat-focus] ;;TODO accessories :options {:topBar {:visible false}} :component new-chat/new-chat} @@ -569,7 +568,7 @@ :component contact/nickname} {:name :new-chat-aio - :on-focus [::new-chat.events/new-chat-focus] + :on-focus [:contacts/new-chat-focus] ;;TODO accessories :options {:topBar {:visible false}} :component new-chat-aio/contact-selection-list} @@ -595,13 +594,6 @@ :component communities.invite/invite :insets {:bottom true}} - ;New Contact - {:name :new-contact - :on-focus [::new-chat.events/new-chat-focus] - ;;TODO accessories - :options {:topBar {:visible false}} - :component new-chat/new-contact} - ;[Wallet] Recipient {:name :recipient :insets {:bottom true} diff --git a/src/status_im/ui/screens/wallet/recipient/views.cljs b/src/status_im/ui/screens/wallet/recipient/views.cljs index 132705eb48..30305af1c0 100644 --- a/src/status_im/ui/screens/wallet/recipient/views.cljs +++ b/src/status_im/ui/screens/wallet/recipient/views.cljs @@ -53,7 +53,7 @@ :on-change (fn [text] (re-frame/dispatch [:search/recipient-filter-changed text]) (re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching]) - (debounce/debounce-and-dispatch [:new-chat/set-new-identity text] 300))}]])) + (debounce/debounce-and-dispatch [:contacts/set-new-identity text] 300))}]])) (defn section [_ _ _] diff --git a/src/status_im/utils/db.cljs b/src/status_im/utils/db.cljs deleted file mode 100644 index 8e54f01578..0000000000 --- a/src/status_im/utils/db.cljs +++ /dev/null @@ -1,5 +0,0 @@ -(ns status-im.utils.db) - -(defn valid-public-key? - [s] - (and (string? s) (not-empty s) (boolean (re-matches #"0x04[0-9a-f]{128}" s)))) \ No newline at end of file diff --git a/src/status_im2/contexts/add_new_contact/events.cljs b/src/status_im2/contexts/add_new_contact/events.cljs new file mode 100644 index 0000000000..2f2596612d --- /dev/null +++ b/src/status_im2/contexts/add_new_contact/events.cljs @@ -0,0 +1,96 @@ +(ns status-im2.contexts.add-new-contact.events + (:require [utils.re-frame :as rf] + [status-im.utils.types :as types] + [re-frame.core :as re-frame] + [status-im.ethereum.core :as ethereum] + [status-im.ethereum.ens :as ens] + [status-im.ethereum.stateofus :as stateofus] + [status-im.native-module.core :as status] + [status-im2.utils.validators :as validators] + [status-im.utils.utils :as utils])) + +(re-frame/reg-fx + :contacts/resolve-public-key-from-ens-name + (fn [{:keys [chain-id ens-name on-success on-error]}] + (ens/pubkey chain-id ens-name on-success on-error))) + +(re-frame/reg-fx + :contacts/decompress-public-key + (fn [{:keys [public-key on-success on-error]}] + (status/compressed-key->public-key + public-key + (fn [resp] + (let [{:keys [error]} (types/json->clj resp)] + (if error + (on-error error) + (on-success + (str "0x" (subs resp 5))))))))) + +(rf/defn set-new-identity + {:events [:contacts/set-new-identity]} + [{:keys [db]} input] + (let [input (utils/safe-trim input) + public-key? (validators/valid-public-key? input) + compressed-key? (validators/valid-compressed-key? input) + ens-name (if compressed-key? nil (stateofus/ens-name-parse input)) + on-success (fn [pubkey] + (rf/dispatch + [:contacts/set-new-identity-success + input ens-name pubkey])) + on-error (fn [err] + (rf/dispatch + [:contacts/set-new-identity-error err input]))] + (cond + (empty? input) {:db (dissoc db :contacts/new-identity)} + public-key? {:db (assoc db + :contacts/new-identity + {:input input + :public-key input + :ens-name nil + :state :error + :error :uncompressed-key})} + :else + (cond-> + {:db (assoc db + :contacts/new-identity + {:input input + :public-key nil + :ens-name nil + :state :searching + :error nil})} + compressed-key? (assoc :contacts/decompress-public-key + {:public-key input + :on-success on-success + :on-error on-error}) + (not compressed-key?) (assoc :contacts/resolve-public-key-from-ens-name + {:chain-id (ethereum/chain-id db) + :ens-name ens-name + :on-success on-success + :on-error on-error}))))) + +(rf/defn set-new-identity-success + {:events [:contacts/set-new-identity-success]} + [{:keys [db]} input ens-name pubkey] + {:db (assoc db + :contacts/new-identity + {:input input + :public-key pubkey + :ens-name ens-name + :state :valid + :error nil})}) + +(rf/defn set-new-identity-error + {:events [:contacts/set-new-identity-error]} + [{:keys [db]} error input] + {:db (assoc db + :contacts/new-identity + {:input input + :public-key nil + :ens-name nil + :state :error + :error :invalid})}) + +(rf/defn clear-new-identity + {:events [:contacts/clear-new-identity :contacts/new-chat-focus]} + [{:keys [db]}] + {:db (dissoc db :contacts/new-identity)}) diff --git a/src/status_im2/contexts/add_new_contact/events_test.cljs b/src/status_im2/contexts/add_new_contact/events_test.cljs new file mode 100644 index 0000000000..87edf99b1c --- /dev/null +++ b/src/status_im2/contexts/add_new_contact/events_test.cljs @@ -0,0 +1,53 @@ +(ns status-im2.contexts.add-new-contact.events-test + (:require [cljs.test :refer-macros [deftest is]] + [status-im2.contexts.add-new-contact.events :as core])) + +(def init-db + {:networks/current-network "mainnet_rpc" + :networks/networks {"mainnet_rpc" + {:id "mainnet_rpc" + :config {:NetworkId 1}}}}) + +(defn search-db + [input] + {:contacts/new-identity {:input input + :public-key nil + :ens-name nil + :state :searching + :error nil}}) + +(deftest search-empty-string + (let [input "" + expected {:db init-db} + actual (core/set-new-identity {:db init-db} input)] + (is (= actual expected)))) + +(deftest search-ens + (let [input "esep" + clean (fn [db] + (-> db + (assoc-in [:contacts/resolve-public-key-from-ens-name :on-success] nil) + (assoc-in [:contacts/resolve-public-key-from-ens-name :on-error] nil))) + expected {:db (merge init-db (search-db input)) + :contacts/resolve-public-key-from-ens-name + {:chain-id 1 + :ens-name (str input ".stateofus.eth") + :on-success nil + :on-error nil}} + actual (core/set-new-identity {:db init-db} input)] + (is (= (clean actual) expected)))) + +(deftest search-compressed-key + (let [input "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gA" + clean (fn [db] + (-> db + (assoc-in [:contacts/decompress-public-key :on-success] nil) + (assoc-in [:contacts/decompress-public-key :on-error] nil))) + expected {:db (merge init-db (search-db input)) + :contacts/decompress-public-key + {:public-key input + :on-success nil + :on-error nil}} + actual (core/set-new-identity {:db init-db} input)] + (is (= (clean actual) expected)))) + diff --git a/src/status_im2/contexts/add_new_contact/style.cljs b/src/status_im2/contexts/add_new_contact/style.cljs new file mode 100644 index 0000000000..8a76c6bbb4 --- /dev/null +++ b/src/status_im2/contexts/add_new_contact/style.cljs @@ -0,0 +1,138 @@ +(ns status-im2.contexts.add-new-contact.style + (:require [quo2.foundations.colors :as colors] + [status-im.react-native.resources :as resources] + [react-native.platform :as platform] + [quo2.foundations.typography :as typography])) + +(defn container-kbd + [] + {:style {:flex 1} + :keyboardVerticalOffset 60}) + +(def container-image + {:style {:flex 1 + :flex-direction :row}}) + +(defn image + [file] + {:source (resources/get-image file) + :style {:flex 1}}) + +(defn container-outer + [] + {:style {:flex (if platform/ios? 4.5 5) + :background-color (colors/theme-colors colors/white colors/neutral-95) + :justify-content :space-between + :align-items :center + :padding 16 + :border-radius 20}}) + +(def container-inner + {:style {:flex-direction :column + :align-items :flex-start}}) + +(def container-text-input + {:style {:flex-direction :row + :justify-content :space-between}}) + +(def container-error + {:style {:flex-direction :row + :align-items :center + :margin-top 8}}) + +(defn text-title + [] + {:size :heading-1 + :weight :semi-bold + :style {:margin-bottom 6 + :color (colors/theme-colors + colors/neutral-100 + colors/white)}}) + +(defn text-subtitle + [] + {:size :paragraph-1 + :weight :regular + :style {:margin-bottom 20 + :color (colors/theme-colors + colors/neutral-100 + colors/white)}}) + +(defn text-description + [] + {:size :paragraph-2 + :weight :medium + :style {:margin-bottom 6 + :color (colors/theme-colors + colors/neutral-50 + colors/neutral-40)}}) + +(def icon-error + {:size 16 + :color colors/danger-50}) + +(def text-error + {:size :paragraph-2 + :align :left + :style {:margin-left 4 + :color colors/danger-50}}) + +(defn text-input + [error?] + {:accessibility-label :enter-contact-code-input + :auto-capitalize :none + :placeholder-text-color (colors/theme-colors + colors/neutral-40 + colors/neutral-50) + :multiline true + :style + (merge typography/monospace + typography/paragraph-1 + {:padding-top 8 + :padding-left 12 + :padding-right 12 + :padding-bottom 8 + :margin-right 10 + :flex 1 + :color (colors/theme-colors + colors/black + colors/white) + :background-color (colors/theme-colors + colors/white + colors/neutral-95) + :border-width 1 + :border-radius 12 + :border-color (if error? + colors/danger-50-opa-40 + (colors/theme-colors + colors/neutral-20 + colors/neutral-80))})}) + +(defn button-close + [] + {:type :grey + :icon true + :accessibility-label :new-contact-close-button + :size 32 + :override-background-color (colors/theme-colors + colors/white-opa-60 + colors/neutral-80-opa-60) + :style {:position :absolute + :left 20 + :top 20}}) + +(def button-qr + {:type :outline + :icon true + :size 40}) + +(defn button-view-profile + [state] + {:type :primary + :size 40 + :width 335 + :style {:margin-top 24 + :margin-bottom 24} + :accessibility-label :new-contact-button + :before :i/profile + :disabled (not= state :valid)}) diff --git a/src/status_im2/contexts/add_new_contact/views.cljs b/src/status_im2/contexts/add_new_contact/views.cljs new file mode 100644 index 0000000000..cd2ada7cfb --- /dev/null +++ b/src/status_im2/contexts/add_new_contact/views.cljs @@ -0,0 +1,56 @@ +(ns status-im2.contexts.add-new-contact.views + (:require [status-im2.contexts.add-new-contact.style :as style] + [utils.i18n :as i18n] + [utils.debounce :as debounce] + [utils.re-frame :as rf] + [react-native.core :as rn] + [quo2.core :as quo])) + +(defn new-contact + [] + (let [{:keys [input public-key state error]} (rf/sub + [:contacts/new-identity]) + error? (and (= state :error) + (= error :uncompressed-key))] + [rn/keyboard-avoiding-view (style/container-kbd) + [rn/view style/container-image + [rn/image (style/image :add-new-contact)] + [quo/button + (merge (style/button-close) + {:on-press + (fn [] + (rf/dispatch [:contacts/clear-new-identity]) + (rf/dispatch [:navigate-back]))}) :i/close]] + [rn/view (style/container-outer) + [rn/view style/container-inner + [quo/text (style/text-title) + (i18n/label :t/add-a-contact)] + [quo/text (style/text-subtitle) + (i18n/label :t/find-your-friends)] + [quo/text (style/text-description) + (i18n/label :t/ens-or-chat-key)] + [rn/view style/container-text-input + [rn/text-input + (merge (style/text-input error?) + {:default-value input + :placeholder (i18n/label :t/type-some-chat-key) + :on-change-text #(debounce/debounce-and-dispatch + [:contacts/set-new-identity %] + 600)})] + [quo/button + (merge style/button-qr + {:on-press #(js/alert "TODO: to be implemented")}) + :i/scan]] + (when error? + [rn/view style/container-error + [quo/icon :i/alert style/icon-error] + [quo/text style/text-error (i18n/label :t/not-a-chatkey)]])] + [rn/view + [quo/button + (merge (style/button-view-profile state) + {:on-press + (fn [] + (rf/dispatch [:contacts/clear-new-identity]) + (rf/dispatch [:navigate-back]) + (rf/dispatch [:chat.ui/show-profile public-key]))}) + (i18n/label :t/view-profile)]]]])) diff --git a/src/status_im2/events.cljs b/src/status_im2/events.cljs index a698d0884e..1575c5bd37 100644 --- a/src/status_im2/events.cljs +++ b/src/status_im2/events.cljs @@ -9,6 +9,7 @@ [status-im2.common.json-rpc.events] [status-im2.common.theme.core :as theme] [status-im2.common.toasts.events] + [status-im2.contexts.add-new-contact.events] [status-im2.navigation.events :as navigation] [status-im2.db :as db] [utils.re-frame :as rf] diff --git a/src/status_im2/navigation/screens.cljs b/src/status_im2/navigation/screens.cljs index af846c947c..bc37ba47e3 100644 --- a/src/status_im2/navigation/screens.cljs +++ b/src/status_im2/navigation/screens.cljs @@ -3,6 +3,7 @@ [status-im.ui.screens.screens :as old-screens] [status-im2.contexts.activity-center.view :as activity-center] [status-im2.contexts.chat.messages.view :as chat] + [status-im2.contexts.add-new-contact.views :as add-new-contact] [status-im2.contexts.communities.discover.view :as communities.discover] [status-im2.contexts.communities.overview.view :as communities.overview] [status-im2.contexts.quo-preview.main :as quo.preview] @@ -47,6 +48,10 @@ :decelerate}}]}}} :component images-horizontal/images-horizontal} + {:name :new-contact + :options {:topBar {:visible false}} + :component add-new-contact/new-contact} + {:name :discover-communities :options {:topBar {:visible false}} :component communities.discover/discover} diff --git a/src/status_im2/utils/validators.cljs b/src/status_im2/utils/validators.cljs new file mode 100644 index 0000000000..cbe2cda0ad --- /dev/null +++ b/src/status_im2/utils/validators.cljs @@ -0,0 +1,14 @@ +(ns status-im2.utils.validators) + +(defn valid-public-key? + [s] + (and (string? s) + (not-empty s) + (boolean (re-matches #"0x04[0-9a-f]{128}" s)))) + +(defn valid-compressed-key? + [s] + (and (string? s) + (not-empty s) + (boolean (re-matches #"^zQ3[0-9A-HJ-NP-Za-km-z]{46}" s)))) + diff --git a/src/status_im2/utils/validators_test.cljs b/src/status_im2/utils/validators_test.cljs new file mode 100644 index 0000000000..3691595bf2 --- /dev/null +++ b/src/status_im2/utils/validators_test.cljs @@ -0,0 +1,33 @@ +(ns status-im2.utils.validators-test + (:require [cljs.test :refer-macros [deftest testing is]] + [status-im2.utils.validators :refer [valid-compressed-key?]])) + +(deftest test-valid-compressed-key + (testing "valid" + (is (valid-compressed-key? + "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gA"))) + (testing "uncompressed key" + (is + (not + (valid-compressed-key? + "0x048a6773339d11ccf5fd81677b7e54daeec544a1287bd92b725047ad6faa9a9b9f8ea86ed5a226d2a994f5f46d0b43321fd8de7b7997a166e67905c8c73cd37ce")))) + (testing "nil" + (is (not (valid-compressed-key? nil)))) + (testing "empty string" + (is (not (valid-compressed-key? "")))) + (testing "too short" + (is (not (valid-compressed-key? "zQ3FGR5y6U5a6")))) + (testing "too long" + (is (not (valid-compressed-key? + "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gA2")))) + (testing "0x prefix" + (is (not (valid-compressed-key? "0xFGR5y6U5a6")))) + (testing "contains I" + (is (not (valid-compressed-key? + "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gI")))) + (testing "contains O" + (is (not (valid-compressed-key? + "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gO")))) + (testing "contains l" + (is (not (valid-compressed-key? + "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gl"))))) diff --git a/translations/en.json b/translations/en.json index 9bac5f667f..de2bd414ea 100644 --- a/translations/en.json +++ b/translations/en.json @@ -570,6 +570,7 @@ "skip": "Skip", "password-placeholder": "Password...", "confirm-password-placeholder": "Confirm your password...", + "ens-or-chat-key": "ENS or Chat key", "enter-pin": "Enter 6-digit passcode", "enter-puk-code": "Enter PUK code", "enter-puk-code-description": "6-digit passcode has been blocked.\n Please enter PUK code to unblock passcode.", @@ -597,6 +598,7 @@ "fetch-messages": "Fetch messages", "fetch-timeline": "↓ Fetch", "find": "Find", + "find-your-friends": "Find your friends with their ENS or Chatkey", "finish": "Finish", "finishing-card-setup": "Finishing card setup", "fleet": "Fleet", @@ -986,6 +988,7 @@ "node-version": "Node version", "nonce": "Nonce", "none": "None", + "not-a-chatkey": "This is not a chatkey", "not-applicable": "Not applicable for unsigned transactions", "not-keycard-text": "The card you used is not a Keycard. You need to purchase a Keycard to use it", "not-keycard-title": "Not a Keycard", @@ -1871,6 +1874,7 @@ "identity-verification-request": "Identity verification", "identity-verification-request-sent": "asks", "type-something": "Type something", + "type-some-chat-key": "0x123abc", "your-answer": "Your answer", "membership": "Membership", "jump-to": "Jump to",