diff --git a/src/status_im/contact/db.cljs b/src/status_im/contact/db.cljs index eaeef9cd443..8b8a99da61d 100644 --- a/src/status_im/contact/db.cljs +++ b/src/status_im/contact/db.cljs @@ -46,7 +46,6 @@ (spec/def :contacts/contacts (spec/nilable (spec/map-of :global/not-empty-string :contact/contact))) ;;public key of new contact during adding this new contact (spec/def :contacts/new-identity (spec/nilable map?)) -(spec/def :contacts/new-identity-error (spec/nilable string?)) ;;on showing this contact's profile (andrey: better to move into profile ns) (spec/def :contacts/identity (spec/nilable :global/not-empty-string)) (spec/def :contacts/list-ui-props (spec/nilable (spec/keys :opt-un [:contact-list-ui/edit?]))) diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index a4c62bde61d..0a492b96c88 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -155,7 +155,6 @@ ;;contacts (reg-root-key-sub ::contacts :contacts/contacts) (reg-root-key-sub :contacts/current-contact-identity :contacts/identity) -(reg-root-key-sub :new-identity-error :contacts/new-identity-error) (reg-root-key-sub :contacts/new-identity :contacts/new-identity) (reg-root-key-sub :group/selected-contacts :group/selected-contacts) ;;wallet diff --git a/src/status_im/ui/screens/add_new/new_chat/db.cljs b/src/status_im/ui/screens/add_new/new_chat/db.cljs index fae24a5ad8c..56460ca60c8 100644 --- a/src/status_im/ui/screens/add_new/new_chat/db.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/db.cljs @@ -1,19 +1,11 @@ (ns status-im.ui.screens.add-new.new-chat.db - (:require [status-im.utils.hex :as hex] - [status-im.utils.platform :as platform] - [status-im.i18n :as i18n] - [cljs.spec.alpha :as spec] - [clojure.string :as string])) + (:require [cljs.spec.alpha :as spec])) (defn own-public-key? [{:keys [multiaccount]} public-key] (= (:public-key multiaccount) public-key)) (defn validate-pub-key [db public-key] - (cond - (not (spec/valid? :global/public-key public-key)) - (i18n/label (if platform/desktop? - :t/use-valid-contact-code-desktop - :t/use-valid-contact-code)) - (own-public-key? db public-key) - (i18n/label :t/can-not-add-yourself))) + (or + (not (spec/valid? :global/public-key public-key)) + (own-public-key? db public-key))) \ No newline at end of file diff --git a/src/status_im/ui/screens/add_new/new_chat/events.cljs b/src/status_im/ui/screens/add_new/new_chat/events.cljs index 34ad94de927..1cb7761ba52 100644 --- a/src/status_im/ui/screens/add_new/new_chat/events.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/events.cljs @@ -6,7 +6,8 @@ [status-im.ethereum.resolver :as resolver] [status-im.ui.screens.add-new.new-chat.db :as db] [status-im.utils.handlers :as handlers] - [status-im.ethereum.stateofus :as stateofus])) + [status-im.ethereum.stateofus :as stateofus] + [status-im.utils.random :as random])) (defn- ens-name-parse [contact-identity] (when (string? contact-identity) @@ -22,18 +23,37 @@ ens-name (ens-name-parse contact-identity)] (resolver/pubkey registry ens-name cb)))) +;;NOTE we want to handle only last resolve +(def resolve-last-id (atom nil)) + (handlers/register-handler-fx :new-chat/set-new-identity - (fn [{db :db} [_ new-identity new-ens-name]] - (let [is-public-key? (and (string? new-identity) - (string/starts-with? new-identity "0x"))] - (merge {:db (assoc db - :contacts/new-identity {:public-key new-identity - :ens-name (ens-name-parse new-ens-name)} - :contacts/new-identity-error (db/validate-pub-key db new-identity))} - (when (and (not is-public-key?) - (ens/valid-eth-name-prefix? new-identity)) - (let [chain (ethereum/chain-keyword db)] - {:resolve-public-key {:chain chain - :contact-identity new-identity - :cb #(re-frame/dispatch [:new-chat/set-new-identity % new-identity])}})))))) + (fn [{db :db} [_ new-identity new-ens-name id]] + (when (or (not id) (= id @resolve-last-id)) + (let [is-public-key? (and (string? new-identity) + (string/starts-with? new-identity "0x")) + is-ens? (and (not is-public-key?) + (ens/valid-eth-name-prefix? new-identity)) + error? (db/validate-pub-key db new-identity)] + (merge {:db (assoc db + :contacts/new-identity + {:public-key new-identity + :state (cond is-ens? + :searching + (and (string/blank? new-identity) (not new-ens-name)) + :empty + error? + :error + :else + :valid) + :ens-name (ens-name-parse new-ens-name)})} + (when is-ens? + (reset! resolve-last-id (random/id)) + (let [chain (ethereum/chain-keyword db)] + {:resolve-public-key + {:chain chain + :contact-identity new-identity + :cb #(re-frame/dispatch [:new-chat/set-new-identity + % + new-identity + @resolve-last-id])}}))))))) \ No newline at end of file diff --git a/src/status_im/ui/screens/add_new/new_chat/navigation.cljs b/src/status_im/ui/screens/add_new/new_chat/navigation.cljs index 309254f709f..cad1b0c38d7 100644 --- a/src/status_im/ui/screens/add_new/new_chat/navigation.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/navigation.cljs @@ -3,4 +3,4 @@ (defmethod navigation/preload-data! :new-chat [db _] - (dissoc db :contacts/new-identity :contacts/new-identity-error)) + (dissoc db :contacts/new-identity)) diff --git a/src/status_im/ui/screens/add_new/new_chat/styles.cljs b/src/status_im/ui/screens/add_new/new_chat/styles.cljs index aefb93b4b4d..ac3debe7196 100644 --- a/src/status_im/ui/screens/add_new/new_chat/styles.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/styles.cljs @@ -1,14 +1,14 @@ (ns status-im.ui.screens.add-new.new-chat.styles (:require [status-im.ui.components.colors :as colors])) -(def error-message - {:margin-horizontal 14 - :margin-top 4 +(def message + {:margin-horizontal 16 + :align-self :center :font-size 12 - :color colors/red}) + :color colors/gray}) (def list-title - {:margin-top 24 - :margin-left 16 - :font-size 14 - :color colors/gray}) + {:margin-top 24 + :margin-left 16 + :font-size 14 + :color colors/gray}) 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 8c4c8d8d511..e579abb2426 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 @@ -6,53 +6,90 @@ [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.list.views :as list] [status-im.ui.components.react :as react] - [status-im.ui.components.toolbar.view :as toolbar.view] [status-im.ui.screens.add-new.styles :as add-new.styles] [status-im.ui.screens.add-new.new-chat.styles :as styles] - [status-im.utils.platform :as platform] [status-im.ui.components.list-item.views :as list-item] [status-im.ui.components.chat-icon.screen :as chat-icon] [status-im.multiaccounts.core :as multiaccounts] - [status-im.ui.components.topbar :as topbar])) + [status-im.ui.components.topbar :as topbar] + [status-im.utils.debounce :as debounce] + [status-im.utils.utils :as utils])) (defn- render-row [row _ _] - [list-item/list-item {:title (multiaccounts/displayed-name row) - :icon [chat-icon/contact-icon-contacts-tab row] - :accessories [:chevron] - :on-press #(re-frame/dispatch [:chat.ui/start-chat (:public-key row) {:navigation-reset? true}])}]) + [list-item/list-item + {:title (multiaccounts/displayed-name row) + :icon [chat-icon/contact-icon-contacts-tab row] + :accessories [:chevron] + :on-press #(re-frame/dispatch [:chat.ui/start-chat + (:public-key row) + {:navigation-reset? true}])}]) + +(defn- icon-wrapper [color icon] + [react/view + {:style {:margin-right 16 + :margin-top 11 + :width 32 + :height 32 + :border-radius 25 + :align-items :center + :justify-content :center + :background-color color}} + icon]) + +(defn- input-icon + [state] + (case state + :searching + [icon-wrapper colors/gray + [react/activity-indicator {:color colors/white}]] + + :valid + [react/touchable-highlight + {:on-press #(debounce/dispatch-and-chill [:contact.ui/contact-code-submitted] 3000)} + [icon-wrapper colors/blue + [vector-icons/icon :main-icons/arrow-right {:color colors/white}]]] + + [icon-wrapper colors/gray + [vector-icons/icon :main-icons/arrow-right {:color colors/white}]])) (views/defview new-chat [] (views/letsubs [contacts [:contacts/active] - new-identity [:contacts/new-identity] - error-message [:new-identity-error]] + {:keys [state ens-name public-key]} [:contacts/new-identity]] [react/view {:style {:flex 1}} - [topbar/topbar {:title :t/new-chat :modal? true}] + [topbar/topbar + {:title :t/new-chat + :modal? true + :accessories [{:icon :qr + :handler #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed + {:title (i18n/label :t/new-contact) + :handler :contact/qr-code-scanned}])}]}] [react/view add-new.styles/new-chat-container [react/view add-new.styles/new-chat-input-container - [react/text-input {:on-change-text #(re-frame/dispatch [:new-chat/set-new-identity %]) - :on-submit-editing #(when (and new-identity (not error-message)) - (re-frame/dispatch [:contact.ui/contact-code-submitted])) - :placeholder (i18n/label :t/enter-contact-code) - :style add-new.styles/input - ;; This input is fine to preserve inputs - ;; so its contents will not be erased - ;; in onWillBlur navigation event handler - :preserve-input? true - :accessibility-label :enter-contact-code-input - :return-key-type :go}]] - (when-not platform/desktop? - [react/touchable-highlight {:on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed - {:title (i18n/label :t/new-contact) - :handler :contact/qr-code-scanned}]) - :style add-new.styles/button-container - :accessibility-label :scan-contact-code-button} - [react/view - [vector-icons/icon :main-icons/camera {:color colors/blue}]]])] - (when error-message - [react/text {:style styles/error-message} - error-message]) - (when (seq contacts) - [list-item/list-item {:title :t/contacts :type :section-header}]) + [react/text-input + {:on-change-text + #(do + (re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching]) + (debounce/debounce-and-dispatch [:new-chat/set-new-identity %] 600)) + :on-submit-editing + #(when (= state :valid) + (debounce/dispatch-and-chill [:contact.ui/contact-code-submitted] 3000)) + :placeholder (i18n/label :t/enter-contact-code) + :style add-new.styles/input + ;; This input is fine to preserve inputs + ;; so its contents will not be erased + ;; in onWillBlur navigation event handler + :preserve-input? true + :accessibility-label :enter-contact-code-input + :return-key-type :go}]] + [react/view {:width 16}] + [input-icon state]] + [react/text {:style styles/message} + (cond (= state :error) + (i18n/label :t/user-not-found) + (= state :valid) + (str (when ens-name (str ens-name " • ")) + (utils/get-shortened-address public-key)) + :else "")] [list/flat-list {:data contacts :key-fn :address :render-fn render-row diff --git a/src/status_im/ui/screens/browser/views.cljs b/src/status_im/ui/screens/browser/views.cljs index 68ec0cc866b..a51a3f22ebe 100644 --- a/src/status_im/ui/screens/browser/views.cljs +++ b/src/status_im/ui/screens/browser/views.cljs @@ -56,7 +56,7 @@ {:browser? true} [toolbar.view/nav-button (actions/close (fn [] - (debounce/clear) + (debounce/clear :browser/navigation-state-changed) (re-frame/dispatch [:navigate-back]) (when error? (re-frame/dispatch [:browser.ui/remove-browser-pressed browser-id]))))] diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index 274501de47a..39a6ddcf847 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -185,7 +185,6 @@ (spec/def ::db (spec/keys :opt [:contacts/contacts :contacts/new-identity - :contacts/new-identity-error :contacts/identity :contacts/ui-props :contacts/list-ui-props diff --git a/src/status_im/utils/debounce.cljs b/src/status_im/utils/debounce.cljs index 68fb31feac6..f66081b57ad 100644 --- a/src/status_im/utils/debounce.cljs +++ b/src/status_im/utils/debounce.cljs @@ -1,24 +1,30 @@ (ns status-im.utils.debounce (:require [re-frame.core :as re-frame])) -(def timeout (atom nil)) +(def timeout (atom {})) + +(defn clear [event-key] + (when-let [event-timeout (get @timeout event-key)] + (js/clearTimeout event-timeout))) + +(defn clear-all [] + (doseq [[_ v] @timeout] + (js/clearTimeout v))) (defn debounce-and-dispatch "Dispatches event only if there were no calls of this function in period of *time* ms" [event time] - (when @timeout (js/clearTimeout @timeout)) - (reset! timeout (js/setTimeout #(re-frame/dispatch event) time))) + (let [event-key (first event)] + (clear event-key) + (swap! timeout assoc event-key (js/setTimeout #(re-frame/dispatch event) time)))) -(defn clear [] - (when @timeout (js/clearTimeout @timeout))) - -(def chill? (atom false)) +(def chill (atom {})) (defn dispatch-and-chill "Dispateches event and ignores next calls in period of *time* ms" [event time] - (when-not @chill? - (reset! chill? true) - (js/setTimeout #(reset! chill? false) time) - (re-frame/dispatch event))) - + (let [event-key (first event)] + (when-not (get @chill event-key) + (swap! chill assoc event-key true) + (js/setTimeout #(swap! chill assoc event-key false) time) + (re-frame/dispatch event)))) \ No newline at end of file diff --git a/translations/en.json b/translations/en.json index 99f29ee380e..3ad436e4d67 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1172,5 +1172,6 @@ "cant-report-bug": "Can't report a bug", "mail-should-be-configured": "Mail client should be configured", "check-on-etherscan": "Check on etherscan", - "transactions-load-more": "Load more" + "transactions-load-more": "Load more", + "user-not-found": "User not found" }