Wallet: Derivation Path - Scanning for Activity (#19482)

Wallet: Derivation Path - Scanning for Activity (#19482)
This commit is contained in:
Omar Basem 2024-04-08 17:53:41 +04:00 committed by GitHub
parent 0e29149363
commit 74e84644aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 167 additions and 150 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

View File

@ -83,7 +83,7 @@
:has-activity {:accessibility-label :account-has-activity
:icon :i/done
:type :success
:message :t/this-address-has-activity}
:message :t/address-activity}
:no-activity {:accessibility-label :account-has-no-activity
:icon :i/info
:type :warning

View File

@ -1,36 +0,0 @@
(ns status-im.contexts.wallet.create-account.edit-derivation-path.component-spec
(:require
[status-im.contexts.wallet.create-account.edit-derivation-path.view :as edit-derivation-path]
[test-helpers.component :as h]))
(def sub-mocks
{:profile/profile {:public-key "123"}
:contacts/contact-two-names-by-identity ["a"]
:profile/image "image"})
(h/describe "Edit derivation path page"
(h/test "Default render"
(h/setup-subs sub-mocks)
(h/render-with-theme-provider [edit-derivation-path/view {}])
(h/is-truthy (h/get-by-translation-text :t/edit-derivation-path))
(h/is-truthy (h/get-by-translation-text :t/path-format))
(h/is-truthy (h/get-by-translation-text :t/derivation-path))
(h/is-truthy (h/get-by-translation-text :t/reveal-address))
(h/is-truthy (h/get-by-translation-text :t/save)))
(h/test "Reveal address pressed"
(h/setup-subs sub-mocks)
(let [on-reveal (h/mock-fn)]
(h/render-with-theme-provider [edit-derivation-path/view {:on-reveal on-reveal}])
(h/fire-event :press (h/get-by-translation-text :t/reveal-address))
(h/was-called on-reveal)
(h/wait-for #(h/is-truthy (h/get-by-translation-text :t/address-activity)))))
(h/test "Reset button pressed"
(h/setup-subs sub-mocks)
(let [on-reset (h/mock-fn)]
(h/render-with-theme-provider [edit-derivation-path/view {:on-reset on-reset}])
(h/fire-event :press (h/get-by-translation-text :t/reset))
(h/was-called on-reset)
(h/wait-for #(h/is-truthy (h/get-by-translation-text :t/derive-addresses))))))

View File

@ -30,9 +30,11 @@
:padding-top 24})
(defn revealed-address
[theme]
[state theme]
{:border-width 1
:border-color (colors/resolve-color :success theme 40)
:border-color (if (= state :scanning)
(colors/theme-colors colors/neutral-20 colors/neutral-70 theme)
(colors/resolve-color (if (= state :has-activity) :success :warning) theme 40))
:border-style :dashed
:border-radius 16
:padding-horizontal 12
@ -42,8 +44,6 @@
{:margin-vertical 9
:padding-left 2})
(def temporal-placeholder
{:height 94
:background-color colors/danger-50
:align-items :center
:justify-content :center})
(defn keyboard
[padding-bottom]
{:padding-bottom padding-bottom})

View File

@ -1,51 +1,68 @@
(ns status-im.contexts.wallet.create-account.edit-derivation-path.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.contexts.wallet.common.temp :as temp]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.create-account.edit-derivation-path.path-format-sheet.view :as
path-format-sheet]
[status-im.contexts.wallet.create-account.edit-derivation-path.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[utils.security.core :as security]))
(defn- view-internal
"States:
default(filled)
| -> (reveal-action) -> show
| -> (clear-action) -> empty -> (derive-action) -> choose -> (choose-action) -> show"
[{:keys [on-reset on-reveal]}]
(let [top (safe-area/get-top)
bottom (safe-area/get-bottom)
state (reagent/atom :default)
reveal-action (fn [_]
(reset! state :show)
(when on-reveal
(on-reveal)))
clear-action #(reset! state :empty)
derive-action #(js/alert "To be implemented")
choose-action #(reset! state :show)
path-value (reagent/atom (utils/get-formatted-derivation-path 3))
handle-path-change (fn [v]
(reset! path-value v)
(when (empty? v)
(clear-action)))
reset-path-value (fn [_]
(reset! path-value "")
(clear-action)
(when on-reset
(on-reset)))]
[{:keys [on-reset]}]
(let [top (safe-area/get-top)
bottom (safe-area/get-bottom)
input-focused? (reagent/atom false)
path-value (reagent/atom "")
input-ref (reagent/atom nil)
reset-path-value (fn [_]
(reset! path-value "")
(when on-reset
(on-reset)))]
(fn [{:keys [theme]}]
(let [{:keys [public-key]} (rf/sub [:profile/profile])
primary-name (first (rf/sub [:contacts/contact-two-names-by-identity
public-key]))
profile-picture (rf/sub [:profile/image])
show-path-format-sheet #(rf/dispatch [:show-bottom-sheet {:content path-format-sheet/view}])]
(let [{:keys [public-key address]} (rf/sub [:profile/profile])
{:keys [password current-derivation-path]} (rf/sub [:get-screen-params])
primary-name (first (rf/sub
[:contacts/contact-two-names-by-identity
public-key]))
profile-picture (rf/sub [:profile/image])
show-path-format-sheet #(rf/dispatch [:show-bottom-sheet
{:content path-format-sheet/view}])
derivation-path (rf/sub [:wallet/derivation-path])
state (rf/sub [:wallet/derivation-path-state])
navigate-back-handler #(if @input-focused?
(do
(.blur ^js @input-ref)
true)
(rf/dispatch [:navigate-to
:screen/wallet.create-account]))
on-change-text #(rf/dispatch
[:wallet/get-derived-addresses
{:password (security/safe-unmask-data
password)
:paths [(string/replace @path-value
#"\s"
"")]
:derived-from address}])]
(rn/use-mount (fn []
(reset! path-value current-derivation-path)
(rf/dispatch [:wallet/get-derived-addresses
{:password (security/safe-unmask-data password)
:paths [(string/replace @path-value #"\s" "")]
:derived-from address}])
(rn/hw-back-add-listener navigate-back-handler)
#(rn/hw-back-remove-listener navigate-back-handler)))
[rn/view
{:style (style/screen top)}
[quo/page-nav
@ -82,54 +99,33 @@
:value (i18n/label :t/default-format)
:container-style {:margin-horizontal 20}}]]
[quo/input
{:container-style style/input-container
:value @path-value
:editable false
:label (i18n/label :t/derivation-path)
:placeholder (utils/get-formatted-derivation-path 3)
:button {:on-press reset-path-value
:text (i18n/label :t/reset)}
:on-change-text handle-path-change}]
(case @state
:default
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/reveal-address)
:button-one-props {:type :outline
:icon-left :i/keycard-card
:on-press reveal-action}}]
:empty
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/derive-addresses)
:button-one-props {:type :outline
:icon-left :i/keycard-card
:on-press derive-action}}]
:show
[rn/view {:style style/revealed-address-container}
[rn/view {:style (style/revealed-address theme)}
[quo/text
{:weight :monospace}
temp/address]]
[quo/info-message
{:type :success
:icon :i/done
:style style/info}
(i18n/label :t/address-activity)]]
:choose
[rn/view {:style style/temporal-placeholder}
[quo/text "Dropdown input will be here"]
[quo/button
{:on-press (fn [_]
(reset! path-value (utils/get-formatted-derivation-path 1))
(choose-action))}
"Choose"]]
nil)
{:ref #(reset! input-ref %)
:container-style style/input-container
:value @path-value
:on-focus #(reset! input-focused? true)
:on-blur #(reset! input-focused? false)
:show-soft-input-on-focus false
:editable true
:label (i18n/label :t/derivation-path)
:placeholder (utils/get-formatted-derivation-path 3)
:button {:on-press reset-path-value
:text (i18n/label :t/reset)}}]
[rn/view {:style style/revealed-address-container}
[rn/view {:style (style/revealed-address state theme)}
[quo/text
{:weight :monospace}
(:address derivation-path)]]
[quo/info-message
{:type (case state
:has-activity :success
:no-activity :warning
:default)
:icon (if (= state :scanning) :i/scanning :i/done)
:style style/info}
(i18n/label (case state
:has-activity :t/address-activity
:no-activity :t/address-no-activity
:t/scanning))]]
[rn/view {:style (style/save-button-container bottom)}
[quo/bottom-actions
{:actions :one-action
@ -137,9 +133,17 @@
:button-one-props {:type :primary
:on-press #(js/alert "Save!")
:disabled? true}}]]
(when-not (= @state :show)
(when @input-focused?
[quo/numbered-keyboard
{:left-action :dot
:delete-key? true}])]))))
{:left-action :dot
:delete-key? true
:container-style (style/keyboard bottom)
:on-press (fn [value]
(reset! path-value (str @path-value value))
(on-change-text))
:on-delete (fn []
(reset! path-value (subs @path-value 0 (dec (count @path-value))))
(on-change-text))}])]))))
(def view (quo.theme/with-theme view-internal))

View File

@ -1,6 +1,8 @@
(ns status-im.contexts.wallet.create-account.events
(:require [status-im.contexts.wallet.data-store :as data-store]
[utils.re-frame :as rf]))
(:require [camel-snake-kebab.extras :as cske]
[status-im.contexts.wallet.data-store :as data-store]
[utils.re-frame :as rf]
[utils.transforms :as transforms]))
(defn get-keypairs-success
[{:keys [db]} [keypairs]]
@ -49,3 +51,23 @@
{:db (update-in db [:wallet :ui :create-account] dissoc :new-keypair)})
(rf/reg-event-fx :wallet/clear-new-keypair clear-new-keypair)
(defn get-derived-addresses
[{:keys [db]} [{:keys [password derived-from paths]}]]
{:db (assoc-in db [:wallet :ui :create-account :derivation-path-state] :scanning)
:json-rpc/call [{:method "wallet_getDerivedAddresses"
:params [password derived-from paths]
:on-success [:wallet/get-derived-addresses-success]}]})
(rf/reg-event-fx :wallet/get-derived-addresses get-derived-addresses)
(defn get-derived-addresses-success
[{:keys [db]} [response]]
(let [derived-address (first response)]
{:db (-> db
(assoc-in [:wallet :ui :create-account :derivation-path-state]
(if (:has-activity derived-address) :has-activity :no-activity))
(assoc-in [:wallet :ui :create-account :derivation-path]
(cske/transform-keys transforms/->kebab-case-keyword derived-address)))}))
(rf/reg-event-fx :wallet/get-derived-addresses-success get-derived-addresses-success)

View File

@ -42,3 +42,14 @@
expected-db {:wallet {:ui {:create-account {}}}}
effects (events/clear-new-keypair {:db db})]
(is (match? (:db effects) expected-db))))
(deftest get-derived-addresses-test
(let [db {}
password "test-password"
derived-from "test-derive-from"
paths ["path1"]
event-args [{:password password :derived-from derived-from :paths paths}]
expected-db (assoc-in db [:wallet :ui :create-account :derivation-path-state] :scanning)
effects (events/get-derived-addresses {:db db} event-args)
result-db (:db effects)]
(is (match? result-db expected-db))))

View File

@ -15,6 +15,7 @@
[status-im.contexts.wallet.create-account.style :as style]
[status-im.contexts.wallet.create-account.utils :as create-account.utils]
[status-im.contexts.wallet.sheets.account-origin.view :as account-origin]
[status-im.feature-flags :as ff]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.responsiveness :as responsiveness]
@ -23,29 +24,39 @@
(defn- get-keypair-data
[{:keys [title primary-keypair? new-keypair? derivation-path customization-color]}]
[{:title title
:image (if primary-keypair? :avatar :icon)
:image-props (if primary-keypair?
{:full-name (utils.string/get-initials title 1)
:size :xxs
:customization-color customization-color}
:i/seed)
:action (when-not new-keypair? :button)
:action-props {:on-press #(rf/dispatch [:navigate-to :screen/wallet.select-keypair])
:button-text (i18n/label :t/edit)
:alignment :flex-start}
:description :text
:description-props {:text (i18n/label :t/on-device)}}
{:title (i18n/label :t/derivation-path)
:image :icon
:image-props :i/derivated-path
:action :button
:action-props {:on-press #(js/alert "Coming soon!")
:button-text (i18n/label :t/edit)
:icon-left :i/placeholder
:alignment :flex-start}
:description :text
:description-props {:text (string/replace derivation-path #"/" " / ")}}])
(let [formatted-path (string/replace derivation-path #"/" " / ")
on-auth-success (fn [password]
(rf/dispatch [:navigate-to
:screen/wallet.edit-derivation-path
{:password password
:current-derivation-path formatted-path}]))]
[{:title title
:image (if primary-keypair? :avatar :icon)
:image-props (if primary-keypair?
{:full-name (utils.string/get-initials title 1)
:size :xxs
:customization-color customization-color}
:i/seed)
:action (when-not new-keypair? :button)
:action-props {:on-press #(rf/dispatch [:navigate-to :screen/wallet.select-keypair])
:button-text (i18n/label :t/edit)
:alignment :flex-start}
:description :text
:description-props {:text (i18n/label :t/on-device)}}
{:title (i18n/label :t/derivation-path)
:image :icon
:image-props :i/derivated-path
:action :button
:action-props {:on-press #(if (ff/enabled? :ff/wallet.edit-derivation-path)
(rf/dispatch [:standard-auth/authorize
{:on-auth-success on-auth-success
:auth-button-label (i18n/label :t/continue)}])
(js/alert "Coming soon!"))
:button-text (i18n/label :t/edit)
:icon-left :i/face-id
:alignment :flex-start}
:description :text
:description-props {:text formatted-path}}]))
(defn- f-view
[_]

View File

@ -6,5 +6,4 @@
[status-im.contexts.shell.share.wallet.component-spec]
[status-im.contexts.wallet.add-address-to-watch.component-spec]
[status-im.contexts.wallet.add-address-to-watch.confirm-address.component-spec]
[status-im.contexts.wallet.create-account.edit-derivation-path.component-spec]
[status-im.contexts.wallet.send.input-amount.component-spec]))

View File

@ -137,6 +137,11 @@
:goerli-enabled? goerli-enabled?})
selected-networks))))
(rf/reg-sub
:wallet/derivation-path-state
:<- [:wallet/create-account]
:-> :derivation-path-state)
(rf/reg-sub
:wallet/accounts
:<- [:wallet]

View File

@ -2458,6 +2458,8 @@
"edit-wallet-network-preferences-updated-message": "Account network preferences has been updated",
"search-assets": "Search assets",
"address-activity": "This address has activity",
"address-no-activity": "This address has no activity",
"scanning": "Scanning for activity...",
"keypairs": "Keypairs",
"keypairs-description": "Select keypair to derive your new account from",
"confirm-account-origin": "Confirm account origin",
@ -2481,7 +2483,6 @@
"est-time": "Est. time",
"user-gets": "{{name}} gets",
"slide-to-send": "Slide to send",
"this-address-has-activity": "This address has activity",
"generate-new-keypair": "Generate new keypair",
"import-using-phrase": "Import using recovery phrase",
"import-from-keycard": "Import from Keycard",