diff --git a/src/status_im/contact/db.cljs b/src/status_im/contact/db.cljs index eaeef9cd44..8b8a99da61 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/events.cljs b/src/status_im/events.cljs index c13b3283c4..99d4495e5b 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -864,6 +864,13 @@ (fn [cofx [_ public-key]] (contact.block/unblock-contact cofx public-key))) +(defn get-validation-label [value] + (case value + :invalid + (i18n/label :t/use-valid-contact-code) + :yourself + (i18n/label :t/can-not-add-yourself))) + (handlers/register-handler-fx :contact/qr-code-scanned [(re-frame/inject-cofx :random-id-generator)] @@ -883,7 +890,7 @@ :else {:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code) - :content validation-result + :content (get-validation-label validation-result) :on-dismiss #(re-frame/dispatch [:navigate-to-clean :home])}})))) (handlers/register-handler-fx diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index bfcbad7120..498b40f030 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -156,7 +156,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 7e74796b73..a61e22c653 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,10 +1,6 @@ (ns status-im.ui.screens.add-new.new-chat.db - (:require [status-im.utils.hex :as hex] - [status-im.ethereum.ens :as ens] - [status-im.utils.platform :as platform] - [status-im.i18n :as i18n] - [cljs.spec.alpha :as spec] - [clojure.string :as string])) + (:require [status-im.ethereum.ens :as ens] + [cljs.spec.alpha :as spec])) (defn own-public-key? [{:keys [multiaccount]} public-key] @@ -14,8 +10,6 @@ (cond (or (not (spec/valid? :global/public-key public-key)) (= public-key ens/default-key)) - (i18n/label (if platform/desktop? - :t/use-valid-contact-code-desktop - :t/use-valid-contact-code)) + :invalid (own-public-key? db public-key) - (i18n/label :t/can-not-add-yourself))) + :yourself)) \ 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 34ad94de92..fc46269866 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,38 @@ 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) + :error error + :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 309254f709..cad1b0c38d 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 aefb93b4b4..ac3debe719 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 8c4c8d8d51..0d7bb1564f 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,98 @@ [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}]])) + +(defn get-validation-label [value] + (case value + :invalid + (i18n/label :t/user-not-found) + :yourself + (i18n/label :t/can-not-add-yourself))) (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 error]} [: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/view {:min-height 30 :justify-content :flex-end} + [react/text {:style styles/message} + (cond (= state :error) + (get-validation-label error) + (= 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/add_new/styles.cljs b/src/status_im/ui/screens/add_new/styles.cljs index 6e0f1d6cf9..9d23af6bae 100644 --- a/src/status_im/ui/screens/add_new/styles.cljs +++ b/src/status_im/ui/screens/add_new/styles.cljs @@ -4,9 +4,9 @@ [status-im.utils.styles :as styles])) (def new-chat-container - {:flex-direction :row - :padding-vertical 16 - :padding-left 16}) + {:flex-direction :row + :padding-top 16 + :padding-left 16}) (def input-container {:flex-direction :row diff --git a/src/status_im/ui/screens/browser/views.cljs b/src/status_im/ui/screens/browser/views.cljs index 68ec0cc866..a51a3f22eb 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 abc6781ecf..306247cca1 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -187,7 +187,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 68fb31feac..f66081b57a 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 e2dfee18f1..7a2e60d588 100644 --- a/translations/en.json +++ b/translations/en.json @@ -101,7 +101,7 @@ "browsing-site-blocked-title": "This site is blocked", "browsing-title": "Browse", "camera-access-error": "To grant the required camera permission, please go to your system settings and make sure that Status > Camera is selected.", - "can-not-add-yourself": "You can't add yourself", + "can-not-add-yourself": "That's you, to start a chat choose someone else", "cancel": "Cancel", "cancel-keycard-setup": "Cancel Keycard setup", "cannot-read-card": "Can't read card.\nPlease hold it to the back of your phone", @@ -1088,5 +1088,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" }