chore(wallet): hook up qr scanner on watch only flow. (#17829)

This commit is contained in:
Jamie Caprani 2023-11-20 00:26:00 +00:00 committed by GitHub
parent c1dcd7a764
commit 86c5505c94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 219 additions and 82 deletions

View File

@ -3,22 +3,17 @@
[quo.components.icon :as quo.icons] [quo.components.icon :as quo.icons]
[quo.components.markdown.text :as text] [quo.components.markdown.text :as text]
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[quo.theme :as theme] [quo.theme :as quo.theme]
[react-native.core :as rn])) [react-native.core :as rn]))
(def themes
{:light {:default colors/neutral-50
:success colors/success-50
:error colors/danger-50}
:dark {:default colors/neutral-40
:success colors/success-60
:error colors/danger-60}})
(defn get-color (defn get-color
[k] [k theme]
(get-in themes [(theme/get-theme) k])) (case k
:success (colors/resolve-color :success theme)
:error (colors/resolve-color :danger theme)
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme)))
(defn info-message (defn view-internal
"[info-message opts \"message\"] "[info-message opts \"message\"]
opts opts
{:type :default/:success/:error {:type :default/:success/:error
@ -27,11 +22,11 @@
:text-color colors/white ;; text color override :text-color colors/white ;; text color override
:icon-color colors/white ;; icon color override :icon-color colors/white ;; icon color override
:no-icon-color? false ;; disable tint color for icon" :no-icon-color? false ;; disable tint color for icon"
[{:keys [type size icon text-color icon-color no-icon-color? style]} message] [{:keys [type size theme icon text-color icon-color no-icon-color? style accessibility-label]} message]
(let [weight (if (= size :default) :regular :medium) (let [weight (if (= size :default) :regular :medium)
icon-size (if (= size :default) 16 12) icon-size (if (= size :default) 16 12)
size (if (= size :default) :paragraph-2 :label) size (if (= size :default) :paragraph-2 :label)
text-color (or text-color (get-color type)) text-color (or text-color (get-color type theme))
icon-color (or icon-color text-color)] icon-color (or icon-color text-color)]
[rn/view [rn/view
{:style (merge {:flex-direction :row {:style (merge {:flex-direction :row
@ -42,7 +37,10 @@
:no-color no-icon-color? :no-color no-icon-color?
:size icon-size}] :size icon-size}]
[text/text [text/text
{:size size {:accessibility-label accessibility-label
:weight weight :size size
:style {:color text-color :weight weight
:margin-horizontal 4}} message]])) :style {:color text-color
:margin-horizontal 4}} message]]))
(def info-message (quo.theme/with-theme view-internal))

View File

@ -31,10 +31,10 @@
:on-change-text #(reset! entered-password %)}] :on-change-text #(reset! entered-password %)}]
(when (not-empty error) (when (not-empty error)
[quo/info-message [quo/info-message
{:type :error {:type :error
:size :default :size :default
:icon :i/info :icon :i/info
:style {:margin-top 8}} :containstyle {:margin-top 8}}
(i18n/label :t/oops-wrong-password)]) (i18n/label :t/oops-wrong-password)])
[quo/button [quo/button
{:container-style {:margin-bottom 12 :margin-top 40} {:container-style {:margin-bottom 12 :margin-top 40}

View File

@ -0,0 +1,38 @@
(ns status-im2.contexts.wallet.add-address-to-watch.component-spec
(:require
[re-frame.core :as re-frame]
[status-im2.contexts.wallet.add-address-to-watch.view :as add-address-to-watch]
[test-helpers.component :as h]))
(defn setup-subs
[subscriptions]
(doseq [keyval subscriptions]
(re-frame/reg-sub
(key keyval)
(fn [_] (val keyval)))))
(h/describe "select address for watch only account"
(h/test "validation messages show for already used addressed"
(setup-subs {:wallet/scanned-address nil
:wallet/addresses (set
["0x12E838Ae1f769147b12956485dc56e57138f3AC8"
"0x22E838Ae1f769147b12956485dc56e57138f3AC8"])
:profile/customization-color :blue})
(h/render [add-address-to-watch/view])
(h/is-falsy (h/query-by-label-text :error-message))
(h/fire-event :change-text
(h/get-by-label-text :add-address-to-watch)
"0x12E838Ae1f769147b12956485dc56e57138f3AC8")
(h/is-truthy (h/get-by-translation-text :address-already-in-use))))
(h/test "validation messages show for invalid address"
(setup-subs {:wallet/scanned-address nil
:wallet/addresses (set
["0x12E838Ae1f769147b12956485dc56e57138f3AC8"
"0x22E838Ae1f769147b12956485dc56e57138f3AC8"])
:profile/customization-color :blue})
(h/render [add-address-to-watch/view])
(h/is-falsy (h/query-by-label-text :error-message))
(h/fire-event :change-text (h/get-by-label-text :add-address-to-watch) "0x12E838Ae1f769147b")
(h/is-truthy (h/get-by-translation-text :invalid-address)))

View File

@ -3,18 +3,7 @@
(def container (def container
{:flex 1}) {:flex 1})
(def input
{:margin-right 12
:flex 1})
(def data-item (def data-item
{:margin-horizontal 20 {:margin-horizontal 20
:padding-vertical 8 :padding-vertical 8
:padding-horizontal 12}) :padding-horizontal 12})
(defn button-container
[bottom]
{:position :absolute
:bottom (+ bottom 12)
:left 20
:right 20})

View File

@ -3,7 +3,6 @@
[clojure.string :as string] [clojure.string :as string]
[quo.core :as quo] [quo.core :as quo]
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent] [reagent.core :as reagent]
@ -14,13 +13,12 @@
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn- view-internal (defn view
[] []
(let [{:keys [address]} (rf/sub [:get-screen-params]) (let [{:keys [address]} (rf/sub [:get-screen-params])
number-of-accounts (count (rf/sub [:profile/wallet-accounts])) number-of-accounts (count (rf/sub [:profile/wallet-accounts]))
account-name (reagent/atom (i18n/label :t/default-account-name account-name (reagent/atom (i18n/label :t/default-account-name
{:number (inc number-of-accounts)})) {:number (inc number-of-accounts)}))
address-title (i18n/label :t/watch-address)
account-color (reagent/atom (rand-nth colors/account-colors)) account-color (reagent/atom (rand-nth colors/account-colors))
account-emoji (reagent/atom (emoji-picker.utils/random-emoji)) account-emoji (reagent/atom (emoji-picker.utils/random-emoji))
on-change-name #(reset! account-name %) on-change-name #(reset! account-name %)
@ -40,8 +38,9 @@
:on-change-name on-change-name :on-change-name on-change-name
:on-change-color on-change-color :on-change-color on-change-color
:on-change-emoji on-change-emoji :on-change-emoji on-change-emoji
:watch-only? true
:bottom-action? true :bottom-action? true
:bottom-action-label :t/create-account :bottom-action-label :t/add-watched-address
:bottom-action-props {:customization-color @account-color :bottom-action-props {:customization-color @account-color
:disabled? (string/blank? @account-name) :disabled? (string/blank? @account-name)
:on-press #(re-frame/dispatch [:navigate-to :on-press #(re-frame/dispatch [:navigate-to
@ -51,11 +50,9 @@
:right-icon :i/advanced :right-icon :i/advanced
:icon-right? true :icon-right? true
:emoji @account-emoji :emoji @account-emoji
:title address-title :title (i18n/label :t/watched-address)
:subtitle address :subtitle address
:status :default :status :default
:size :default :size :default
:container-style style/data-item :container-style style/data-item
:on-press #(js/alert "To be implemented")}]]]))) :on-press #(js/alert "To be implemented")}]]])))
(def view (quo.theme/with-theme view-internal))

View File

@ -5,13 +5,24 @@
:margin-top 12 :margin-top 12
:margin-bottom 20}) :margin-bottom 20})
(def input-container
{:flex-direction :row
:padding-horizontal 20
:align-items :flex-end})
(def button-container (def button-container
{:position :absolute {:position :absolute
:bottom 22 :bottom 22
:left 20 :left 20
:right 20}) :right 20})
(def scan
{:align-self
:flex-end})
(def input
{:flex 1
:margin-right 12})
(def input-container
{:flex-direction :row
:margin-horizontal 20})
(def info-message
{:margin-top 8
:margin-left 20})

View File

@ -2,51 +2,114 @@
(:require (:require
[clojure.string :as string] [clojure.string :as string]
[quo.core :as quo] [quo.core :as quo]
[quo.theme :as quo.theme]
[re-frame.core :as re-frame]
[react-native.clipboard :as clipboard] [react-native.clipboard :as clipboard]
[react-native.core :as rn] [react-native.core :as rn]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im2.contexts.wallet.add-address-to-watch.style :as style] [status-im2.contexts.wallet.add-address-to-watch.style :as style]
[status-im2.contexts.wallet.common.validation :as validation]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn view-internal (defn validate-message
[addresses]
(fn [s]
(cond
(or (= s nil) (= s "")) nil
(contains? addresses s) (i18n/label :t/address-already-in-use)
(not (or (validation/eth-address? s)
(validation/ens-name? s))) (i18n/label :t/invalid-address)
:else nil)))
(defn- address-input
[{:keys [input-value validation-msg validate
clear-input]}]
(let [scanned-address (rf/sub [:wallet/scanned-address])
empty-input? (and (string/blank? @input-value)
(string/blank? scanned-address))
on-change-text (fn [new-text]
(reset! validation-msg (validate new-text))
(reset! input-value new-text)
(when (and scanned-address (not= scanned-address new-text))
(rf/dispatch [:wallet/clean-scanned-address])))
paste-on-input #(clipboard/get-string
(fn [clipboard-text]
(on-change-text clipboard-text)))]
(rn/use-effect (fn []
(when-not (string/blank? scanned-address)
(on-change-text scanned-address)))
[scanned-address])
[rn/view
{:style style/input-container}
[quo/input
{:accessibility-label :add-address-to-watch
:placeholder (i18n/label :t/address-placeholder)
:container-style style/input
:label (i18n/label :t/eth-or-ens)
:auto-capitalize :none
:multiline? true
:on-clear clear-input
:return-key-type :done
:clearable? (not empty-input?)
:on-change-text on-change-text
:button (when empty-input?
{:on-press paste-on-input
:text (i18n/label :t/paste)})
:value @input-value}]
[quo/button
{:type :outline
:on-press (fn []
(rn/dismiss-keyboard!)
(rf/dispatch [:open-modal :scan-address]))
:container-style style/scan
:size 40
:icon-only? true}
:i/scan]]))
(defn view
[] []
(let [input-value (reagent/atom "") (let [addresses (rf/sub [:wallet/addresses])
input-value (reagent/atom nil)
validate (validate-message (set addresses))
validation-msg (reagent/atom (validate
@input-value))
clear-input (fn []
(reset! input-value nil)
(reset! validation-msg nil)
(rf/dispatch [:wallet/clean-scanned-address]))
customization-color (rf/sub [:profile/customization-color])] customization-color (rf/sub [:profile/customization-color])]
(rf/dispatch [:wallet/clean-scanned-address])
(fn [] (fn []
[rn/view [rn/view
{:style {:flex 1}} {:style {:flex 1}}
[quo/page-nav [quo/page-nav
{:type :no-title {:type :no-title
:icon-name :i/close :icon-name :i/close
:on-press #(rf/dispatch [:navigate-back])}] :on-press (fn []
(rf/dispatch [:wallet/clean-scanned-address])
(rf/dispatch [:navigate-back]))}]
[quo/text-combinations [quo/text-combinations
{:container-style style/header-container {:container-style style/header-container
:title (i18n/label :t/add-address) :title (i18n/label :t/add-address)
:description (i18n/label :t/enter-eth)}] :description (i18n/label :t/enter-eth)}]
[rn/view {:style style/input-container} [:f> address-input
[quo/input {:input-value input-value
{:label (i18n/label :t/eth-or-ens) :validate validate
:button {:on-press (fn [] (clipboard/get-string #(reset! input-value %))) :validation-msg validation-msg
:text (i18n/label :t/paste)} :clear-input clear-input}]
:placeholder (str "0x123abc... " (string/lower-case (i18n/label :t/or)) " bob.eth") (when @validation-msg
:container-style {:margin-right 12 [quo/info-message
:flex 1} {:accessibility-label :error-message
:weight :monospace :size :default
:on-change-text #(reset! input-value %) :icon :i/info
:default-value @input-value}] :type :error
[quo/button :style style/info-message}
{:icon-only? true @validation-msg])
:type :outline} :i/scan]]
[quo/button [quo/button
{:customization-color customization-color {:customization-color customization-color
:disabled? (clojure.string/blank? @input-value) :disabled? (string/blank? @input-value)
:on-press #(re-frame/dispatch [:navigate-to :on-press #(rf/dispatch [:navigate-to
:confirm-address-to-watch :confirm-address-to-watch
{:address @input-value}]) {:address @input-value}])
:container-style style/button-container} :container-style style/button-container}
(i18n/label :t/continue)]]))) (i18n/label :t/continue)]])))
(def view (quo.theme/with-theme view-internal))

View File

@ -1,6 +1,5 @@
(ns status-im2.contexts.wallet.common.screen-base.create-or-edit-account.view (ns status-im2.contexts.wallet.common.screen-base.create-or-edit-account.view
(:require [quo.core :as quo] (:require [quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.safe-area :as safe-area] [react-native.safe-area :as safe-area]
[status-im2.constants :as constants] [status-im2.constants :as constants]
@ -8,12 +7,12 @@
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn- view-internal (defn view
[{:keys [margin-top? page-nav-right-side account-name account-color account-emoji on-change-name [{:keys [margin-top? page-nav-right-side account-name account-color account-emoji on-change-name
on-change-color on-change-color
on-change-emoji on-focus on-blur section-label bottom-action? on-change-emoji on-focus on-blur section-label bottom-action?
bottom-action-label bottom-action-props bottom-action-label bottom-action-props
custom-bottom-action]} & children] custom-bottom-action watch-only?]} & children]
(let [{:keys [top bottom]} (safe-area/get-insets) (let [{:keys [top bottom]} (safe-area/get-insets)
margin-top (if (false? margin-top?) 0 top) margin-top (if (false? margin-top?) 0 top)
{window-width :width} (rn/get-window)] {window-width :width} (rn/get-window)]
@ -36,7 +35,7 @@
{:customization-color account-color {:customization-color account-color
:size 80 :size 80
:emoji account-emoji :emoji account-emoji
:type :default}] :type (if watch-only? :watch-only :default)}]
[quo/button [quo/button
{:size 32 {:size 32
:type :grey :type :grey
@ -81,5 +80,3 @@
:type :primary} :type :primary}
bottom-action-props) bottom-action-props)
(i18n/label bottom-action-label)])])])) (i18n/label bottom-action-label)])])]))
(def view (quo.theme/with-theme view-internal))

View File

@ -1,13 +1,16 @@
(ns status-im2.contexts.wallet.common.sheets.account-options.view (ns status-im2.contexts.wallet.common.sheets.account-options.view
(:require [quo.core :as quo] (:require [quo.core :as quo]
[quo.foundations.colors :as colors]
quo.theme
[react-native.clipboard :as clipboard]
[react-native.core :as rn] [react-native.core :as rn]
[status-im2.contexts.wallet.common.sheets.account-options.style :as style] [status-im2.contexts.wallet.common.sheets.account-options.style :as style]
[status-im2.contexts.wallet.common.temp :as temp] [status-im2.contexts.wallet.common.temp :as temp]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(defn view (defn- view-internal
[] [{:keys [theme]}]
(let [{:keys [name color emoji address]} (rf/sub [:wallet/current-viewing-account])] (let [{:keys [name color emoji address]} (rf/sub [:wallet/current-viewing-account])]
[:<> [:<>
[quo/drawer-top [quo/drawer-top
@ -26,7 +29,13 @@
:on-press #(rf/dispatch [:navigate-to :wallet-edit-account])} :on-press #(rf/dispatch [:navigate-to :wallet-edit-account])}
{:icon :i/copy {:icon :i/copy
:accessibility-label :copy-address :accessibility-label :copy-address
:label (i18n/label :t/copy-address)} :label (i18n/label :t/copy-address)
:on-press (fn []
(rf/dispatch [:toasts/upsert
{:icon :i/correct
:icon-color (colors/resolve-color :success theme)
:text (i18n/label :t/address-copied)}])
(clipboard/set-string address))}
{:icon :i/share {:icon :i/share
:accessibility-label :share-account :accessibility-label :share-account
:label (i18n/label :t/share-account)} :label (i18n/label :t/share-account)}
@ -42,3 +51,5 @@
{:data temp/other-accounts {:data temp/other-accounts
:render-fn (fn [account] [quo/account-item {:account-props account}]) :render-fn (fn [account] [quo/account-item {:account-props account}])
:style {:margin-horizontal 8}}]])) :style {:margin-horizontal 8}}]]))
(def view (quo.theme/with-theme view-internal))

View File

@ -0,0 +1,5 @@
(ns status-im2.contexts.wallet.common.validation
(:require [status-im2.constants :as constants]))
(defn ens-name? [s] (re-find constants/regx-ens s))
(defn eth-address? [s] (re-find constants/regx-address s))

View File

@ -48,7 +48,7 @@
(fn [] (fn []
(let [scanned-address (rf/sub [:wallet/scanned-address]) (let [scanned-address (rf/sub [:wallet/scanned-address])
send-address (rf/sub [:wallet/send-address]) send-address (rf/sub [:wallet/send-address])
valid-ens-or-address? (boolean (rf/sub [:wallet/valid-ens-or-address?]))] valid-ens-or-address? (rf/sub [:wallet/valid-ens-or-address?])]
[quo/address-input [quo/address-input
{:on-focus #(reset! input-focused? true) {:on-focus #(reset! input-focused? true)
:on-blur #(reset! input-focused? false) :on-blur #(reset! input-focused? false)

View File

@ -3,4 +3,5 @@
[status-im2.common.floating-button-page.component-spec] [status-im2.common.floating-button-page.component-spec]
[status-im2.contexts.chat.messages.content.audio.component-spec] [status-im2.contexts.chat.messages.content.audio.component-spec]
[status-im2.contexts.communities.actions.community-options.component-spec] [status-im2.contexts.communities.actions.community-options.component-spec]
[status-im2.contexts.wallet.add-address-to-watch.component-spec]
[status-im2.contexts.wallet.create-account.edit-derivation-path.component-spec])) [status-im2.contexts.wallet.create-account.edit-derivation-path.component-spec]))

View File

@ -21,6 +21,14 @@
vals vals
(sort-by :position))) (sort-by :position)))
(rf/reg-sub
:wallet/addresses
:<- [:wallet]
:-> #(->> %
:accounts
keys
set))
(rf/reg-sub (rf/reg-sub
:wallet/balances :wallet/balances
:<- [:wallet/accounts] :<- [:wallet/accounts]

View File

@ -167,3 +167,17 @@
:balance 3250 :balance 3250
:tokens tokens-0x1} :tokens tokens-0x1}
(rf/sub [sub-name]))))) (rf/sub [sub-name])))))
(h/deftest-sub :wallet/addresses
[sub-name]
(testing "returns all addresses"
(swap! rf-db/app-db
#(-> %
(assoc-in [:wallet :accounts] accounts)
(assoc-in [:wallet :current-viewing-account-address] "0x1")))
(is
(= (set ["0x1" "0x2"])
(rf/sub [sub-name])))))

View File

@ -1565,7 +1565,8 @@
"delete-account": "Delete account", "delete-account": "Delete account",
"delete-keys-keycard": "Delete keys from Keycard", "delete-keys-keycard": "Delete keys from Keycard",
"watch-only": "Watch-only", "watch-only": "Watch-only",
"watch-address": "Watch address", "watched-address": "Watched address",
"add-watched-address": "Add watched address",
"cant-report-bug": "Can't report a bug", "cant-report-bug": "Can't report a bug",
"mail-should-be-configured": "Mail client should be configured", "mail-should-be-configured": "Mail client should be configured",
"check-on-block-explorer": "Check on block explorer", "check-on-block-explorer": "Check on block explorer",
@ -2384,5 +2385,9 @@
"address-activity": "This address has activity", "address-activity": "This address has activity",
"keypairs": "Keypairs", "keypairs": "Keypairs",
"keypairs-description": "Select keypair to derive your new account from", "keypairs-description": "Select keypair to derive your new account from",
"confirm-account-origin": "Confirm account origin" "confirm-account-origin": "Confirm account origin",
"address-placeholder": "0x123abc... or bob.eth",
"invalid-address": "Its not Ethereum address or ENS name",
"address-already-in-use": "Address already being used",
"address-copied": "Address copied"
} }