[#9789] Add a loading indicator and button to username input of start new chat

Signed-off-by: Andrey Shovkoplyas <motor4ik@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2020-02-06 13:14:15 +01:00
parent 87ce82e10a
commit d64ba1b540
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
13 changed files with 160 additions and 89 deletions

View File

@ -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?])))

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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])}})))))))

View File

@ -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))

View File

@ -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})

View File

@ -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

View File

@ -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

View File

@ -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]))))]

View File

@ -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

View File

@ -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))))

View File

@ -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"
}