Key management screen in place
Key phrase screen in place Added flow in a Rich comment, added storage screen, added Keycard upsell banner Validate seed against selected multiaccount Vvalidate seed against multiaccount Connected migration flow to Keycard onboarding flow, unable to finish because an event called generate-and-load-key is not being emitted with the flow I made Fixed state that was needed to start the recovery process, also removed seed-phrase from app-db when onboarding starts Moved strings to translations in key storage views Upsell banner bg theme, accordion data, choose-storage next bug, Hide keys managment option for Keycard accounts and from multiaccounts list screen. Added test for subscription function Tests for keystore move checkbox and seed phrase input Fix translations/en.json after merge conflict, add test for seed-phrase input, seed-against-key-uid validation and state setup for redirect to Keycard screens Update validate fn to mock mnemonic import and cascading changes Fix linting errors Fix seed key-uid mismatch popup, reduce styles and remove redundant views Add dot menu in place of access-existing-keys button on login page Make multiaccount redirect test more terse Remove dangling comma from translations/en.json Fix var that was missed during rebase fx/merge and extra newline fixes Removed nil? check, tests pass Unknown error popup Redirect Keycard banner to https://keycard.tech Remove unused sub and fix linting Fix issue#4 - Unable to enter new seeds Hide back and disable hardware back on Keycard onboarding intro if the user is coming from migration flow. Fixes issue#1 raised by Diana. Set state so show wallet balances show up auto magically. Fixes issue#3. Update tests to capture recovering state Center align sign in button since the left button was deleted. Changes suggested by Andrey Add top margin to Keycard intro only when user comes key storage Hide manage key storage on ios Signed-off-by: Shivek Khurana <shivek@status.im>
This commit is contained in:
parent
f28f479256
commit
5c3133adb6
Binary file not shown.
After Width: | Height: | Size: 698 B |
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
|
@ -1,5 +1,6 @@
|
|||
(ns status-im.keycard.core
|
||||
(:require [status-im.keycard.change-pin :as change-pin]
|
||||
(:require [re-frame.db]
|
||||
[status-im.keycard.change-pin :as change-pin]
|
||||
[status-im.keycard.common :as common]
|
||||
status-im.keycard.delete-key
|
||||
status-im.keycard.export-key
|
||||
|
@ -528,3 +529,16 @@
|
|||
{:events [:keycard.callback/on-register-card-events]}
|
||||
[{:keys [db]} listeners]
|
||||
{:db (update-in db [:keycard :listeners] merge listeners)})
|
||||
|
||||
(defn onboarding-intro-back-handler
|
||||
"The back button handler is used to manage device back press.
|
||||
|
||||
If the handler returns false, the back button functions as usual (ie. dispatchs GO_BACK event).
|
||||
If it returns true, the back button becomes inactive.
|
||||
|
||||
We want to deactivate the back button when the user comes from key-storage and migration flow."
|
||||
[]
|
||||
(-> @re-frame.db/app-db
|
||||
:keycard
|
||||
:from-key-storage-and-migration?
|
||||
boolean))
|
||||
|
|
|
@ -15,6 +15,12 @@
|
|||
[taoensso.timbre :as log]
|
||||
[clojure.string :as string]))
|
||||
|
||||
;; validate that the given mnemonic was generated from Status Dictionary
|
||||
(re-frame/reg-fx
|
||||
::validate-mnemonic
|
||||
(fn [[passphrase callback]]
|
||||
(native-module/validate-mnemonic passphrase callback)))
|
||||
|
||||
(defn contact-names
|
||||
"Returns map of all existing names for contact"
|
||||
[{:keys [name preferred-name alias public-key ens-verified nickname]}]
|
||||
|
@ -163,7 +169,7 @@
|
|||
(defn clean-path [path]
|
||||
(if path
|
||||
(string/replace-first path #"file://" "")
|
||||
(log/warn "[nativ-module] Empty path was provided")))
|
||||
(log/warn "[native-module] Empty path was provided")))
|
||||
|
||||
(fx/defn save-profile-picture
|
||||
{:events [::save-profile-picture]}
|
||||
|
@ -201,3 +207,7 @@
|
|||
{:events [::update-local-picture]}
|
||||
[cofx pics]
|
||||
(multiaccounts.update/optimistic cofx :images pics))
|
||||
|
||||
(comment
|
||||
;; Test seed for Dim Venerated Yaffle, it's not here by mistake, this is just a test account
|
||||
(native-module/validate-mnemonic "rocket mixed rebel affair umbrella legal resemble scene virus park deposit cargo" prn))
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
(ns status-im.multiaccounts.key-storage.core
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.ethereum.mnemonic :as mnemonic]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.multiaccounts.recover.core :as multiaccounts.recover]
|
||||
[status-im.multiaccounts.model :as multiaccounts.model]
|
||||
[status-im.native-module.core :as native-module]
|
||||
[status-im.navigation :as navigation]
|
||||
[status-im.popover.core :as popover]
|
||||
[status-im.utils.fx :as fx]
|
||||
[status-im.utils.security :as security]
|
||||
[status-im.utils.types :as types]))
|
||||
|
||||
(fx/defn key-and-storage-management-pressed
|
||||
"This event can be dispatched before login and from profile and needs to redirect accordingly"
|
||||
{:events [::key-and-storage-management-pressed]}
|
||||
[cofx]
|
||||
(navigation/navigate-to-cofx
|
||||
cofx
|
||||
:key-storage-stack
|
||||
{:screen (if (multiaccounts.model/logged-in? cofx)
|
||||
:actions-logged-in
|
||||
:actions-not-logged-in)}))
|
||||
|
||||
(fx/defn move-keystore-checked
|
||||
{:events [::move-keystore-checked]}
|
||||
[{:keys [db] :as cofx} checked?]
|
||||
{:db (assoc-in db [:multiaccounts/key-storage :move-keystore-checked?] checked?)})
|
||||
|
||||
(fx/defn enter-seed-pressed
|
||||
"User is logged out and probably wants to move multiaccount to Keycard. Navigate to enter seed phrase screen"
|
||||
{:events [::enter-seed-pressed]}
|
||||
[cofx]
|
||||
(navigation/navigate-to-cofx cofx :key-storage-stack {:screen :seed-phrase}))
|
||||
|
||||
(fx/defn seed-phrase-input-changed
|
||||
{:events [::seed-phrase-input-changed]}
|
||||
[{:keys [db] :as cofx} masked-seed-phrase]
|
||||
(let [seed-phrase (security/safe-unmask-data masked-seed-phrase)]
|
||||
{:db (update db :multiaccounts/key-storage assoc
|
||||
:seed-phrase (when seed-phrase
|
||||
(string/lower-case seed-phrase))
|
||||
:seed-shape-invalid? (or (empty? seed-phrase)
|
||||
(not (mnemonic/valid-length? seed-phrase)))
|
||||
:seed-word-count (mnemonic/words-count seed-phrase))}))
|
||||
|
||||
(fx/defn key-uid-seed-mismatch
|
||||
{:events [::show-seed-key-uid-mismatch-error-popup]}
|
||||
[cofx _]
|
||||
(popover/show-popover cofx {:view :seed-key-uid-mismatch}))
|
||||
|
||||
(defn validate-seed-against-key-uid
|
||||
"Check if the key-uid was generated with the given seed-phrase"
|
||||
[{:keys [import-mnemonic-fn on-success on-error]} {:keys [seed-phrase key-uid]}]
|
||||
(import-mnemonic-fn
|
||||
seed-phrase nil
|
||||
(fn [result]
|
||||
(let [{:keys [keyUid]} (types/json->clj result)]
|
||||
;; if the key-uid from app-db is same as the one returned by multiaccount import,
|
||||
;; it means that this seed was used to generate this multiaccount
|
||||
(if (= key-uid keyUid)
|
||||
(on-success)
|
||||
(on-error))))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::validate-seed-against-key-uid
|
||||
(partial validate-seed-against-key-uid
|
||||
{:import-mnemonic-fn native-module/multiaccount-import-mnemonic
|
||||
:on-success #(re-frame/dispatch [:navigate-to :storage])
|
||||
:on-error #(re-frame/dispatch [::show-seed-key-uid-mismatch-error-popup])}))
|
||||
|
||||
(fx/defn seed-phrase-validated
|
||||
{:events [::seed-phrase-validated]}
|
||||
[{:keys [db] :as cofx} validation-error]
|
||||
(let [error? (-> validation-error
|
||||
types/json->clj
|
||||
:error
|
||||
string/blank?
|
||||
not)]
|
||||
(if error?
|
||||
(popover/show-popover cofx {:view :custom-seed-phrase})
|
||||
{::validate-seed-against-key-uid {:seed-phrase (-> db :multiaccounts/key-storage :seed-phrase)
|
||||
;; Unique key-uid of the account for which we are going to move keys
|
||||
:key-uid (-> db :multiaccounts/login :key-uid)}})))
|
||||
|
||||
(fx/defn choose-storage-pressed
|
||||
{:events [::choose-storage-pressed]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [{:keys [seed-phrase]} (:multiaccounts/key-storage db)]
|
||||
{::multiaccounts/validate-mnemonic
|
||||
[(mnemonic/sanitize-passphrase seed-phrase)
|
||||
#(re-frame/dispatch [::seed-phrase-validated %])]}))
|
||||
|
||||
(fx/defn keycard-storage-pressed
|
||||
{:events [::keycard-storage-pressed]}
|
||||
[{:keys [db]} selected?]
|
||||
{:db (assoc-in db [:multiaccounts/key-storage :keycard-storage-selected?] selected?)})
|
||||
|
||||
(fx/defn warning-popup
|
||||
{:events [::show-transfer-warning-popup]}
|
||||
[cofx]
|
||||
(popover/show-popover cofx {:view :transfer-multiaccount-to-keycard-warning}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::delete-multiaccount
|
||||
(fn [{:keys [key-uid on-success on-error]}]
|
||||
(native-module/delete-multiaccount
|
||||
key-uid
|
||||
(fn [result]
|
||||
(let [{:keys [error]} (types/json->clj result)]
|
||||
(if-not (string/blank? error)
|
||||
(on-error error)
|
||||
(on-success)))))))
|
||||
|
||||
(fx/defn delete-multiaccount-and-init-keycard-onboarding
|
||||
{:events [::delete-multiaccount-and-init-keycard-onboarding]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [{:keys [key-uid]} (-> db :multiaccounts/login)]
|
||||
{::delete-multiaccount {:key-uid key-uid
|
||||
:on-error #(re-frame/dispatch [::delete-multiaccount-error %])
|
||||
:on-success #(re-frame/dispatch [::delete-multiaccount-success])}}))
|
||||
|
||||
#_"Multiaccount has been deleted from device. We now need to emulate the restore seed phrase process, and make the user land on Keycard setup screen.
|
||||
To ensure that keycard setup works, we need to:
|
||||
1. Import multiaccount, derive required keys and save them at the correct location in app-db
|
||||
2. Take the user to :keycard-onboarding-intro screen in :intro-login-stack
|
||||
|
||||
The exact events dispatched for this flow if consumed from the UI are:
|
||||
:m.r/enter-phrase-input-changed
|
||||
:m.r/enter-phrase-next-pressed
|
||||
:m.r/re-encrypt-pressed
|
||||
:i/on-key-storage-selected ([:intro-wizard :selected-storage-type] is set to :advanced)
|
||||
:m.r/select-storage-next-pressed
|
||||
|
||||
We don't need to take the exact steps, just set the required state and redirect to correct screen
|
||||
"
|
||||
(fx/defn handle-delete-multiaccount-success
|
||||
{:events [::delete-multiaccount-success]}
|
||||
[{:keys [db] :as cofx} _]
|
||||
{::multiaccounts.recover/import-multiaccount {:passphrase (get-in db [:multiaccounts/key-storage :seed-phrase])
|
||||
:password nil
|
||||
:success-event ::import-multiaccount-success}})
|
||||
|
||||
(fx/defn handle-multiaccount-import
|
||||
{:events [::import-multiaccount-success]}
|
||||
[{:keys [db] :as cofx} root-data derived-data]
|
||||
(fx/merge cofx
|
||||
{:db (-> db
|
||||
(update :intro-wizard
|
||||
assoc
|
||||
:root-key root-data
|
||||
:derived derived-data
|
||||
:recovering? true
|
||||
:selected-storage-type :advanced)
|
||||
(assoc-in [:keycard :flow] :recovery)
|
||||
(assoc-in [:keycard :from-key-storage-and-migration?] true)
|
||||
(dissoc :multiaccounts/key-storage))}
|
||||
(popover/hide-popover)
|
||||
(navigation/navigate-to-cofx :intro-stack {:screen :keycard-onboarding-intro})))
|
||||
|
||||
(fx/defn handle-delete-multiaccount-error
|
||||
{:events [::delete-multiaccount-error]}
|
||||
[cofx _]
|
||||
(popover/show-popover cofx {:view :transfer-multiaccount-unknown-error}))
|
||||
|
||||
(fx/defn goto-multiaccounts-screen
|
||||
{:events [::hide-popover-and-goto-multiaccounts-screen]}
|
||||
[cofx _]
|
||||
(fx/merge cofx
|
||||
(popover/hide-popover)
|
||||
(navigation/navigate-to-cofx :intro-stack {:screen :multiaccounts})))
|
||||
|
||||
(comment
|
||||
;; check import mnemonic output
|
||||
(native-module/multiaccount-import-mnemonic "rocket mixed rebel affair umbrella legal resemble scene virus park deposit cargo" nil
|
||||
(fn [result]
|
||||
(prn (types/json->clj result))))
|
||||
;; check delete account output
|
||||
(native-module/delete-multiaccount "0x3831d0f22996a65970a214f0a94bfa9a63a21dac235d8dadb91be8e32e7d3ab7"
|
||||
(fn [result]
|
||||
(prn ::--delete-account-res-> result))))
|
|
@ -0,0 +1,66 @@
|
|||
(ns status-im.multiaccounts.key-storage.core-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[clojure.string :as string]
|
||||
[status-im.multiaccounts.key-storage.core :as models]
|
||||
[status-im.utils.security :as security]))
|
||||
|
||||
(deftest move-keystore-checked
|
||||
(testing "Checks checkbox on-press"
|
||||
(let [res (models/move-keystore-checked {:db {}} true)]
|
||||
(is (= true (get-in res [:db :multiaccounts/key-storage :move-keystore-checked?]))))))
|
||||
|
||||
(deftest seed-phrase-input-changed
|
||||
(testing "nil seed phrase shape is invalid"
|
||||
(let [res (models/seed-phrase-input-changed {:db {}} (security/mask-data nil))]
|
||||
(is (get-in res [:db :multiaccounts/key-storage :seed-shape-invalid?]))))
|
||||
|
||||
(let [sample-phrase "h h h h h h h h h h h H" ;; 12 characters
|
||||
res (models/seed-phrase-input-changed {:db {}} (security/mask-data sample-phrase))]
|
||||
(testing "Seed shape for 12 letter seed phrase is valid"
|
||||
(is (false? (get-in res [:db :multiaccounts/key-storage :seed-shape-invalid?]))))
|
||||
|
||||
(testing "Seed words counted correctly"
|
||||
(is (= 12 (get-in res [:db :multiaccounts/key-storage :seed-word-count]))))
|
||||
|
||||
(testing "Seed phrase is lowercased"
|
||||
(is (= (get-in res [:db :multiaccounts/key-storage :seed-phrase])
|
||||
(string/lower-case sample-phrase))))))
|
||||
|
||||
(def seed-key-uid-pair
|
||||
{:seed-phrase "rocket mixed rebel affair umbrella legal resemble scene virus park deposit cargo"
|
||||
:key-uid "0x3831d0f22996a65970a214f0a94bfa9a63a21dac235d8dadb91be8e32e7d3ab7"})
|
||||
|
||||
(defn mock-import-mnemonic-fn [_ _ _]
|
||||
;; return json with keyUid, the real world will have more info in the response
|
||||
(str "{\"keyUid\": \"" (:key-uid seed-key-uid-pair) "\"}"))
|
||||
|
||||
(deftest validate-seed-against-key-uid
|
||||
(testing "Success event is triggered if correct seed is entered for selected multiaccount (key-uid)"
|
||||
(models/validate-seed-against-key-uid
|
||||
{:import-mnemonic-fn mock-import-mnemonic-fn
|
||||
:on-success #(is true) ; this callback should be called
|
||||
:on-error #(is false)}
|
||||
{:seed-phrase (:seed-phrase seed-key-uid-pair)
|
||||
:key-uid (:key-uid seed-key-uid-pair)}))
|
||||
|
||||
(testing "Error event is triggered if incorrect seed is entered for selected multiaccount"
|
||||
(models/validate-seed-against-key-uid
|
||||
{:import-mnemonic-fn mock-import-mnemonic-fn
|
||||
:on-success #(is false)
|
||||
:on-error #(is true)}
|
||||
{:seed-phrase (:seed-phrase seed-key-uid-pair)
|
||||
:key-uid "0xInvalid-Will-make-the-function-fail"})))
|
||||
|
||||
(deftest handle-multiaccount-import
|
||||
(testing "Sets correct state for Keycard onboarding after multiaccounts seeds are derived"
|
||||
(let [res (models/handle-multiaccount-import {:db {}} :passed-root-data :passed-derived-data)]
|
||||
(is (= :passed-root-data (get-in res [:db :intro-wizard :root-key])))
|
||||
(is (= :passed-derived-data (get-in res [:db :intro-wizard :derived])))
|
||||
(is (= :advanced (get-in res [:db :intro-wizard :selected-storage-type]))) ; :advanced storage type means Keycard
|
||||
(is (= :recovery (get-in res [:db :keycard :flow])))
|
||||
(is (get-in res [:db :keycard :from-key-storage-and-migration?]))
|
||||
(is (= {:intro-stack {:screen :keycard-onboarding-intro}} (get-in res [:db :navigation/screen-params]))))))
|
||||
|
||||
(comment
|
||||
(security/safe-unmask-data (security/mask-data nil)))
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
[status-im.ethereum.mnemonic :as mnemonic]
|
||||
[status-im.keycard.nfc :as nfc]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.multiaccounts.create.core :as multiaccounts.create]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.popover.core :as popover]
|
||||
|
@ -24,11 +25,6 @@
|
|||
{:pre [(not (nil? key-uid))]}
|
||||
(contains? multiaccounts key-uid))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::validate-mnemonic
|
||||
(fn [[passphrase callback]]
|
||||
(status/validate-mnemonic passphrase callback)))
|
||||
|
||||
(defn check-phrase-warnings [recovery-phrase]
|
||||
(cond (string/blank? recovery-phrase) :t/required-field))
|
||||
|
||||
|
@ -87,7 +83,7 @@
|
|||
|
||||
(re-frame/reg-fx
|
||||
::import-multiaccount
|
||||
(fn [{:keys [passphrase password]}]
|
||||
(fn [{:keys [passphrase password success-event]}]
|
||||
(log/debug "[recover] ::import-multiaccount")
|
||||
(status/multiaccount-import-mnemonic
|
||||
passphrase
|
||||
|
@ -113,8 +109,7 @@
|
|||
(update derived-data
|
||||
constants/path-whisper-keyword
|
||||
merge {:name name :identicon identicon})]
|
||||
(re-frame/dispatch [::import-multiaccount-success
|
||||
root-data derived-data-extended]))))))))))))
|
||||
(re-frame/dispatch [success-event root-data derived-data-extended]))))))))))))
|
||||
|
||||
(fx/defn show-existing-multiaccount-alert
|
||||
[_ key-uid]
|
||||
|
@ -168,22 +163,24 @@
|
|||
(if-not (string/blank? (:error (types/json->clj phrase-warnings)))
|
||||
(popover/show-popover cofx {:view :custom-seed-phrase})
|
||||
(when (mnemonic/valid-length? passphrase)
|
||||
{::import-multiaccount {:passphrase (mnemonic/sanitize-passphrase passphrase)
|
||||
:password password}}))))
|
||||
{::import-multiaccount {:passphrase (mnemonic/sanitize-passphrase passphrase)
|
||||
:password password
|
||||
:success-event ::import-multiaccount-success}}))))
|
||||
|
||||
(fx/defn seed-phrase-next-pressed
|
||||
{:events [:multiaccounts.recover/enter-phrase-next-pressed]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [{:keys [passphrase]} (:intro-wizard db)]
|
||||
{::validate-mnemonic [passphrase #(re-frame/dispatch [:multiaccounts.recover/phrase-validated %])]}))
|
||||
{::multiaccounts/validate-mnemonic [passphrase #(re-frame/dispatch [:multiaccounts.recover/phrase-validated %])]}))
|
||||
|
||||
(fx/defn continue-to-import-mnemonic
|
||||
{:events [::continue-pressed]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [{:keys [password passphrase]} (:multiaccounts/recover db)]
|
||||
(fx/merge cofx
|
||||
{::import-multiaccount {:passphrase passphrase
|
||||
:password password}}
|
||||
{::import-multiaccount {:passphrase passphrase
|
||||
:password password
|
||||
:success-event ::import-multiaccount-success}}
|
||||
(popover/hide-popover))))
|
||||
|
||||
(fx/defn dec-step
|
||||
|
|
|
@ -107,6 +107,8 @@
|
|||
(reg-root-key-sub :multiaccount :multiaccount)
|
||||
(reg-root-key-sub :multiaccount/accounts :multiaccount/accounts)
|
||||
(reg-root-key-sub :get-recover-multiaccount :multiaccounts/recover)
|
||||
(reg-root-key-sub :multiaccounts/key-storage :multiaccounts/key-storage)
|
||||
|
||||
;;chat
|
||||
(reg-root-key-sub ::cooldown-enabled? :chat/cooldown-enabled?)
|
||||
(reg-root-key-sub ::chats :chats)
|
||||
|
@ -339,6 +341,19 @@
|
|||
(fn [[intro-wizard multiaccounts]]
|
||||
(recover/existing-account? (:root-key intro-wizard) multiaccounts)))
|
||||
|
||||
(defn login-ma-keycard-pairing
|
||||
"Compute the keycard-pairing value of the multiaccount selected for login"
|
||||
[db _]
|
||||
(when-let [acc-to-login (-> db :multiaccounts/login)]
|
||||
(-> db
|
||||
:multiaccounts/multiaccounts
|
||||
(get (:key-uid acc-to-login))
|
||||
:keycard-pairing)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:intro-wizard/acc-to-login-keycard-pairing
|
||||
login-ma-keycard-pairing)
|
||||
|
||||
(re-frame/reg-sub
|
||||
:current-network
|
||||
:<- [:networks/networks]
|
||||
|
|
|
@ -20,3 +20,20 @@
|
|||
(testing "Check if transactions are sorted by date"
|
||||
(is (= (#'status-im.subs/group-transactions-by-date transactions)
|
||||
grouped-transactions))))
|
||||
|
||||
(deftest login-ma-keycard-pairing
|
||||
(testing "returns nil when no :multiaccounts/login"
|
||||
(let [res (status-im.subs/login-ma-keycard-pairing
|
||||
{:multiaccounts/login nil
|
||||
:multiaccounts/multiaccounts
|
||||
{"0x1" {:keycard-pairing "keycard-pairing-code"}}}
|
||||
{})]
|
||||
(is (nil? res))))
|
||||
|
||||
(testing "returns :keycard-pairing when :multiaccounts/login is present"
|
||||
(let [res (status-im.subs/login-ma-keycard-pairing
|
||||
{:multiaccounts/login {:key-uid "0x1"}
|
||||
:multiaccounts/multiaccounts
|
||||
{"0x1" {:keycard-pairing "keycard-pairing-code"}}}
|
||||
{})]
|
||||
(is (= res "keycard-pairing-code")))))
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
(ns status-im.ui.components.accordion
|
||||
(:require [reagent.core :as reagent]
|
||||
[quo.core :as quo]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.icons.vector-icons :as icons]))
|
||||
|
||||
(defn section
|
||||
"Render collapsible section"
|
||||
[_props]
|
||||
(let [opened? (reagent/atom false)]
|
||||
(fn [{:keys [title cnt content icon]}]
|
||||
[react/view {:padding-vertical 8}
|
||||
[quo/list-item
|
||||
{:title title
|
||||
:icon icon
|
||||
:on-press #(swap! opened? not)
|
||||
:accessory
|
||||
[react/view {:flex-direction :row :align-items :center}
|
||||
(when (pos? cnt)
|
||||
[react/text {:style {:color colors/gray}} cnt])
|
||||
[icons/icon (if @opened? :main-icons/dropdown-up :main-icons/dropdown)
|
||||
{:container-style {:align-items :center
|
||||
:margin-left 8
|
||||
:justify-content :center}
|
||||
:resize-mode :center
|
||||
:color colors/black}]]}]
|
||||
(when @opened?
|
||||
content)])))
|
||||
|
||||
(defn accordion
|
||||
"List of collapseable sections"
|
||||
[]
|
||||
;; TODO(shivekkhurana): Extract status-im.ui.screens.wallet.recipient.views/accordion component here
|
||||
)
|
|
@ -372,7 +372,7 @@
|
|||
[top-bar {:step :enter-phrase}]
|
||||
[enter-phrase wizard-state]
|
||||
[bottom-bar (merge {:step :enter-phrase
|
||||
:forward-action :multiaccounts.recover/enter-phrase-next-pressed}
|
||||
:forward-action :multiaccounts.recover/enter-phrase-next-pressed}
|
||||
wizard-state)]]]))
|
||||
|
||||
(defview wizard-recovery-success []
|
||||
|
|
|
@ -16,12 +16,15 @@
|
|||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||
|
||||
(defview intro []
|
||||
(letsubs [flow [:keycard-flow]]
|
||||
(letsubs [flow [:keycard-flow]
|
||||
{:keys [from-key-storage-and-migration?]} [:keycard]]
|
||||
[react/view styles/container
|
||||
[topbar/topbar]
|
||||
(when-not from-key-storage-and-migration?
|
||||
[topbar/topbar])
|
||||
[react/view {:flex 1
|
||||
:justify-content :space-between
|
||||
:align-items :center}
|
||||
:align-items :center
|
||||
:margin-top (when from-key-storage-and-migration? 80)}
|
||||
[react/view {:align-items :center}
|
||||
[react/view
|
||||
[react/view {:align-items :center
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
(ns status-im.ui.screens.multiaccounts.key-storage.styles
|
||||
(:require [status-im.ui.components.colors :as colors]))
|
||||
|
||||
(def help-text-container
|
||||
{:width "60%"
|
||||
:align-self :center
|
||||
:padding-vertical 24})
|
||||
|
||||
(def help-text
|
||||
{:text-align :center})
|
||||
|
||||
(def popover-title
|
||||
{:typography :title-bold
|
||||
:margin-top 8
|
||||
:margin-bottom 24})
|
||||
|
||||
(def popover-body-container
|
||||
{:flex-wrap :wrap
|
||||
:flex-direction :row
|
||||
:justify-content :center
|
||||
:text-align :center})
|
||||
|
||||
(def popover-text
|
||||
{:color colors/gray
|
||||
:text-align :center
|
||||
:line-height 22})
|
|
@ -0,0 +1,336 @@
|
|||
(ns status-im.ui.screens.multiaccounts.key-storage.views
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(:require [quo.core :as quo]
|
||||
[re-frame.core :as re-frame]
|
||||
[re-frame.db]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.multiaccounts.key-storage.core :as multiaccounts.key-storage]
|
||||
[status-im.react-native.resources :as resources]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
|
||||
[status-im.ui.components.topbar :as topbar]
|
||||
[status-im.ui.components.toolbar :as toolbar]
|
||||
[status-im.ui.components.accordion :as accordion]
|
||||
[status-im.ui.screens.multiaccounts.views :as multiaccounts.views]
|
||||
[status-im.ui.screens.multiaccounts.key-storage.styles :as styles]
|
||||
[status-im.utils.security]))
|
||||
|
||||
(defn local-topbar [subtitle]
|
||||
[topbar/topbar {:title (i18n/label :t/key-managment)
|
||||
:subtitle subtitle}])
|
||||
|
||||
(defonce accordian-data
|
||||
[{:id :type
|
||||
:label (i18n/label :t/type)
|
||||
:value (i18n/label :t/master-account)}
|
||||
{:id :back-up
|
||||
:label (i18n/label :t/back-up)
|
||||
:value (i18n/label :t/recovery-phrase)}
|
||||
{:id :storage
|
||||
:label (i18n/label :t/storage)
|
||||
:value (i18n/label :t/key-on-device)}])
|
||||
|
||||
(defn accordion-content []
|
||||
[react/view {:padding-horizontal 16
|
||||
:flex-direction :row}
|
||||
[react/view {:flex-shrink 0
|
||||
:margin-right 20}
|
||||
(for [{:keys [id label]} accordian-data]
|
||||
^{:key (str "left-" id)}
|
||||
[react/text {:style {:color colors/gray
|
||||
:padding-vertical 8}} label])]
|
||||
|
||||
[react/view {:flex 1}
|
||||
(for [{:keys [id value]} accordian-data]
|
||||
^{:key (str "right-" id)}
|
||||
[react/text {:flex 1
|
||||
:flex-wrap :wrap
|
||||
:style {:padding-vertical 8}}
|
||||
value])]])
|
||||
|
||||
;; Component to render Key and Storage management screen
|
||||
(defview actions-base [{:keys [next-title next-event]}]
|
||||
(letsubs [{:keys [name] :as multiaccount} [:multiaccounts/login]
|
||||
{:keys [move-keystore-checked?]} [:multiaccounts/key-storage]]
|
||||
[react/view {:flex 1}
|
||||
[local-topbar (i18n/label :t/choose-actions)]
|
||||
[accordion/section {:title name
|
||||
:icon [chat-icon.screen/contact-icon-contacts-tab
|
||||
(multiaccounts/displayed-photo multiaccount)]
|
||||
:count 0
|
||||
:content [accordion-content]}]
|
||||
[react/view {:flex 1
|
||||
:flex-direction :column
|
||||
:justify-content :space-between}
|
||||
[react/view
|
||||
[quo/list-header (i18n/label :t/actions)]
|
||||
[quo/list-item {:title (i18n/label :t/move-keystore-file)
|
||||
:subtitle (i18n/label :t/select-new-location-for-keys)
|
||||
:subtitle-max-lines 4
|
||||
:accessory :checkbox
|
||||
:active move-keystore-checked?
|
||||
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/move-keystore-checked (not move-keystore-checked?)])}]
|
||||
[quo/list-item {:title (i18n/label :t/reset-database)
|
||||
:subtitle (i18n/label :t/reset-database-warning)
|
||||
:subtitle-max-lines 4
|
||||
:disabled true
|
||||
:active move-keystore-checked?
|
||||
:accessory :checkbox}]]
|
||||
(when (and next-title next-event)
|
||||
[toolbar/toolbar {:show-border? true
|
||||
:right [quo/button
|
||||
{:type :secondary
|
||||
:disabled (not move-keystore-checked?)
|
||||
:on-press #(re-frame/dispatch next-event)
|
||||
:after :main-icons/next}
|
||||
next-title]}])]]))
|
||||
|
||||
(defn actions-not-logged-in
|
||||
"To be used when the flow is accessed before login, will enter seed phrase next"
|
||||
[]
|
||||
[actions-base {:next-title (i18n/label :t/enter-seed-phrase)
|
||||
:next-event [::multiaccounts.key-storage/enter-seed-pressed]}])
|
||||
|
||||
(defn actions-logged-in
|
||||
"To be used when the flow is accessed from profile, will choose storage next"
|
||||
[]
|
||||
[actions-base {:next-title (i18n/label :t/choose-storage)
|
||||
:next-event [::multiaccounts.key-storage/choose-storage-pressed]}])
|
||||
|
||||
(defview seed-phrase []
|
||||
(letsubs
|
||||
[{:keys [seed-word-count seed-shape-invalid?]} [:multiaccounts/key-storage]]
|
||||
[react/keyboard-avoiding-view {:flex 1}
|
||||
[local-topbar (i18n/label :t/enter-seed-phrase)]
|
||||
[multiaccounts.views/seed-phrase-input
|
||||
{:on-change-event [::multiaccounts.key-storage/seed-phrase-input-changed]
|
||||
:seed-word-count seed-word-count
|
||||
:seed-shape-invalid? seed-shape-invalid?}]
|
||||
[react/text {:style {:color colors/gray
|
||||
:font-size 14
|
||||
:margin-bottom 8
|
||||
:text-align :center}}
|
||||
(i18n/label :t/multiaccounts-recover-enter-phrase-text)]
|
||||
[toolbar/toolbar {:show-border? true
|
||||
:right [quo/button
|
||||
{:type :secondary
|
||||
:disabled (or seed-shape-invalid?
|
||||
(nil? seed-shape-invalid?))
|
||||
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/choose-storage-pressed])
|
||||
:after :main-icons/next}
|
||||
(i18n/label :t/choose-storage)]}]]))
|
||||
|
||||
(defn keycard-subtitle []
|
||||
[react/view
|
||||
[react/text {:style {:color colors/gray}} (i18n/label :t/empty-keycard-required)]
|
||||
[react/view {:flex-direction :row
|
||||
:align-items :center}
|
||||
[react/text {:style {:color colors/blue}
|
||||
:accessibility-label :learn-more
|
||||
:on-press #(js/alert :press)}
|
||||
(i18n/label :learn-more)]
|
||||
[vector-icons/icon :main-icons/tiny-external {:color colors/blue
|
||||
:width 16
|
||||
:height 16}]]])
|
||||
|
||||
(defn keycard-upsell-banner []
|
||||
[react/touchable-highlight {:on-press #(.openURL ^js react/linking "https://keycard.tech/")}
|
||||
[react/view {:background-color (if (= :dark @colors/theme) "#2C5955" "#DDF8F4")
|
||||
:border-radius 16
|
||||
:margin 16
|
||||
:padding-horizontal 12
|
||||
:padding-vertical 8
|
||||
:flex-direction :row}
|
||||
[react/view
|
||||
[react/image {:source (resources/get-theme-image :keycard)
|
||||
:resize-mode :contain
|
||||
:style {:width 48
|
||||
:height 48}}]]
|
||||
[react/view {:flex 1
|
||||
:margin-left 12}
|
||||
[react/text {:style {:font-size 20
|
||||
:font-weight "700"}}
|
||||
(i18n/label :t/get-a-keycard)]
|
||||
[react/text {:style {:color (colors/alpha colors/text 0.8)}}
|
||||
(i18n/label :t/keycard-upsell-subtitle)]]]])
|
||||
|
||||
(defview storage []
|
||||
(letsubs
|
||||
[{:keys [keycard-storage-selected?]} [:multiaccounts/key-storage]]
|
||||
[react/view {:flex 1}
|
||||
[local-topbar (i18n/label :t/choose-storage)]
|
||||
[react/view {:style styles/help-text-container}
|
||||
[react/text {:style styles/help-text}
|
||||
(i18n/label :t/choose-new-location-for-keystore)]]
|
||||
[react/view
|
||||
[quo/list-header (i18n/label :t/current)]
|
||||
[quo/list-item {:title (i18n/label :t/this-device)
|
||||
:text-size :base
|
||||
:icon :main-icons/mobile
|
||||
:disabled true}]
|
||||
[quo/list-header (i18n/label :t/new)]
|
||||
[quo/list-item {:title (i18n/label :t/keycard)
|
||||
:subtitle (i18n/label :t/empty-keycard-required)
|
||||
:subtitle-max-lines 4
|
||||
:icon :main-icons/keycard
|
||||
:active keycard-storage-selected?
|
||||
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/keycard-storage-pressed (not keycard-storage-selected?)])
|
||||
:accessory :radio}]]
|
||||
[react/view {:flex 1
|
||||
:justify-content :flex-end}
|
||||
(when-not keycard-storage-selected?
|
||||
[keycard-upsell-banner])
|
||||
[toolbar/toolbar {:show-border? true
|
||||
:right [quo/button
|
||||
{:type :secondary
|
||||
:disabled (not keycard-storage-selected?)
|
||||
:on-press #(re-frame/dispatch [::multiaccounts.key-storage/show-transfer-warning-popup])}
|
||||
(i18n/label :t/confirm)]}]]]))
|
||||
|
||||
(defview seed-key-uid-mismatch-popover []
|
||||
(letsubs [{:keys [name]} [:multiaccounts/login]]
|
||||
[react/view {:margin-top 24
|
||||
:margin-horizontal 24
|
||||
:align-items :center}
|
||||
[react/view {:width 32
|
||||
:height 32
|
||||
:border-radius 16
|
||||
:align-items :center
|
||||
:justify-content :center}
|
||||
[vector-icons/icon :main-icons/warning {:color colors/blue}]]
|
||||
[react/text {:style {:typography :title-bold
|
||||
:margin-top 8
|
||||
:margin-bottom 24}}
|
||||
(i18n/label :t/seed-key-uid-mismatch)]
|
||||
[react/view styles/popover-body-container
|
||||
[react/view
|
||||
[react/text {:style (into styles/popover-text
|
||||
{:margin-bottom 16})}
|
||||
(i18n/label :t/seed-key-uid-mismatch-desc-1 {:multiaccount-name name})]
|
||||
[react/text {:style styles/popover-text}
|
||||
(i18n/label :t/seed-key-uid-mismatch-desc-2)]]]
|
||||
[react/view {:margin-vertical 24
|
||||
:align-items :center}
|
||||
[quo/button {:on-press #(re-frame/dispatch [:hide-popover])
|
||||
:accessibility-label :cancel-custom-seed-phrase
|
||||
:type :secondary}
|
||||
(i18n/label :t/try-again)]]]))
|
||||
|
||||
(defview transfer-multiaccount-warning-popover []
|
||||
[react/view {:margin-top 24
|
||||
:margin-horizontal 24
|
||||
:align-items :center}
|
||||
[react/view {:width 32
|
||||
:height 32
|
||||
:border-radius 16
|
||||
:align-items :center
|
||||
:justify-content :center}
|
||||
[vector-icons/icon :main-icons/tiny-warning-background {:color colors/red}]]
|
||||
[react/text {:style styles/popover-title}
|
||||
(i18n/label :t/move-keystore-file-to-keycard)]
|
||||
[react/view styles/popover-body-container
|
||||
[react/text {:style styles/popover-text}
|
||||
(i18n/label :t/database-reset-warning)]]
|
||||
[react/view {:margin-vertical 24
|
||||
:align-items :center}
|
||||
[quo/button {:on-press #(re-frame/dispatch [::multiaccounts.key-storage/delete-multiaccount-and-init-keycard-onboarding])
|
||||
:accessibility-label :cancel-custom-seed-phrase
|
||||
:type :primary
|
||||
:theme :negative}
|
||||
(i18n/label :t/move-and-reset)]
|
||||
[quo/button {:on-press #(re-frame/dispatch [:hide-popover])
|
||||
:accessibility-label :cancel-custom-seed-phrase
|
||||
:type :secondary}
|
||||
(i18n/label :t/cancel)]]])
|
||||
|
||||
(defview unknown-error-popover []
|
||||
[react/view {:margin-top 24
|
||||
:margin-horizontal 24
|
||||
:align-items :center}
|
||||
[react/view {:width 32
|
||||
:height 32
|
||||
:border-radius 16
|
||||
:align-items :center
|
||||
:justify-content :center}
|
||||
[vector-icons/icon :main-icons/close {:color colors/red}]]
|
||||
[react/text {:style {:typography :title-bold
|
||||
:margin-top 8
|
||||
:margin-bottom 24}}
|
||||
(i18n/label :t/something-went-wrong)]
|
||||
[react/view styles/popover-body-container
|
||||
[react/view
|
||||
[react/text {:style (into styles/popover-text
|
||||
{:margin-bottom 16})}
|
||||
(i18n/label :t/transfer-ma-unknown-error-desc-1)]
|
||||
[react/text {:style styles/popover-text}
|
||||
(i18n/label :t/transfer-ma-unknown-error-desc-2)]]]
|
||||
[react/view {:margin-vertical 24
|
||||
:align-items :center}
|
||||
[quo/button {:on-press #(re-frame/dispatch [::multiaccounts.key-storage/hide-popover-and-goto-multiaccounts-screen])
|
||||
:type :secondary}
|
||||
(i18n/label :t/okay)]]])
|
||||
|
||||
(comment
|
||||
;; UI flow
|
||||
(do
|
||||
;; Goto key management actions screen
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/key-and-storage-management-pressed])
|
||||
|
||||
;; Check move key store checkbox
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/move-keystore-checked true])
|
||||
|
||||
;; Goto enter seed screen
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/enter-seed-pressed])
|
||||
|
||||
;; Enter seed phrase
|
||||
|
||||
;; invalid seed shape
|
||||
#_(re-frame/dispatch [::multiaccounts.key-storage/seed-phrase-input-changed (status-im.utils.security/mask-data "h h h h h h h h h h h h")])
|
||||
|
||||
;; valid seed for Trusty Candid Bighornedsheep
|
||||
;; If you try to select Dim Venerated Yaffle, but use this seed instead, validate-seed-against-key-uid will fail miserably
|
||||
#_(re-frame/dispatch [::multiaccounts.key-storage/seed-phrase-input-changed
|
||||
(status-im.utils.security/mask-data "disease behave roof exile ghost head carry item tumble census rocket champion")])
|
||||
|
||||
;; valid seed for Swiffy Warlike Seagull
|
||||
#_(re-frame/dispatch [::multiaccounts.key-storage/seed-phrase-input-changed
|
||||
(status-im.utils.security/mask-data "dirt agent garlic merge tuna leaf congress hedgehog absent dish pizza scrap")])
|
||||
|
||||
;; valid seed for Dim Venerated Yaffle (this is just a test account, okay to leak seed)
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/seed-phrase-input-changed
|
||||
(status-im.utils.security/mask-data "rocket mixed rebel affair umbrella legal resemble scene virus park deposit cargo")])
|
||||
|
||||
;; Click choose storage
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/choose-storage-pressed])
|
||||
|
||||
;; Choose Keycard from storage options
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/keycard-storage-pressed true])
|
||||
|
||||
;; Confirm migration popup
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/show-transfer-warning-popup])
|
||||
|
||||
;; Delete multiaccount and init keycard onboarding
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/delete-multiaccount-and-init-keycard-onboarding]))
|
||||
|
||||
|
||||
;; Show error popup
|
||||
|
||||
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/show-seed-key-uid-mismatch-error-popup])
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/show-transfer-warning-popup])
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/delete-multiaccount-error])
|
||||
(re-frame/dispatch [:hide-popover])
|
||||
|
||||
;; Flow to populate state after multiaccount is deleted
|
||||
(do
|
||||
;; set seed phrase for Dim Venerated Yaffle
|
||||
(re-frame/dispatch [:set-in [:multiaccounts/key-storage :seed-phrase] "rocket mixed rebel affair umbrella legal resemble scene virus park deposit cargo"])
|
||||
|
||||
;; set seed for Trusty Candid Bighornedsheep
|
||||
#_(re-frame/dispatch [:set-in [:multiaccounts/key-storage :seed-phrase] "disease behave roof exile ghost head carry item tumble census rocket champion"])
|
||||
|
||||
;; simulate delete multiaccount success
|
||||
(re-frame/dispatch [::multiaccounts.key-storage/delete-multiaccount-success])))
|
|
@ -45,7 +45,11 @@
|
|||
view-id [:view-id]
|
||||
supported-biometric-auth [:supported-biometric-auth]]
|
||||
[react/keyboard-avoiding-view {:style ast/multiaccounts-view}
|
||||
[topbar/topbar {:border-bottom false}]
|
||||
[topbar/topbar {:border-bottom false
|
||||
:right-accessories [{:icon :more
|
||||
:on-press #(do
|
||||
(react/dismiss-keyboard!)
|
||||
(re-frame/dispatch [:multiaccounts.recover.ui/recover-multiaccount-button-pressed]))}]}]
|
||||
[react/scroll-view {:keyboardShouldPersistTaps :always
|
||||
:style styles/login-view}
|
||||
[react/view styles/login-badge-container
|
||||
|
@ -93,18 +97,10 @@
|
|||
[react/i18n-text {:style styles/processing :key :processing}]])
|
||||
|
||||
[toolbar/toolbar
|
||||
{:show-border? true
|
||||
:size :large
|
||||
:left
|
||||
[quo/button
|
||||
{:type :secondary
|
||||
:on-press #(do
|
||||
(react/dismiss-keyboard!)
|
||||
(re-frame/dispatch [:multiaccounts.recover.ui/recover-multiaccount-button-pressed]))}
|
||||
(i18n/label :t/access-existing-keys)]
|
||||
:right
|
||||
{:size :large
|
||||
:center
|
||||
[react/view {:padding-horizontal 8}
|
||||
[quo/button
|
||||
{:disabled (or (not sign-in-enabled?) processing)
|
||||
:on-press #(login-multiaccount @password-text-input)}
|
||||
(i18n/label :t/submit)]]}]]))
|
||||
(i18n/label :t/sign-in)]]}]]))
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
(ns status-im.ui.screens.multiaccounts.recover.views
|
||||
(:require-macros [status-im.utils.views :refer [defview]])
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.multiaccounts.recover.core :as multiaccounts.recover]
|
||||
[status-im.multiaccounts.key-storage.core :as multiaccounts.key-storage]
|
||||
[status-im.keycard.recovery :as keycard]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.utils.security]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[quo.core :as quo]
|
||||
[status-im.utils.platform :as platform]
|
||||
|
@ -42,32 +44,71 @@
|
|||
:type :secondary}
|
||||
(i18n/label :t/cancel)]]]])
|
||||
|
||||
(defn bottom-sheet-view []
|
||||
[react/view {:flex 1 :flex-direction :row}
|
||||
[react/view {:flex 1}
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/enter-seed-phrase)
|
||||
:accessibility-label :enter-seed-phrase-button
|
||||
:icon :main-icons/text
|
||||
:on-press #(hide-sheet-and-dispatch [::multiaccounts.recover/enter-phrase-pressed])}]
|
||||
(when (or platform/android?
|
||||
config/keycard-test-menu-enabled?)
|
||||
(defview bottom-sheet-view []
|
||||
(letsubs [view-id [:view-id]
|
||||
acc-to-login-keycard-pairing [:intro-wizard/acc-to-login-keycard-pairing]]
|
||||
[react/view {:flex 1 :flex-direction :row}
|
||||
[react/view {:flex 1}
|
||||
;; Show manage storage link when on login screen, only on android devices
|
||||
;; and the selected account is not paired with keycard
|
||||
(when (and (= view-id :login)
|
||||
platform/android?
|
||||
(not acc-to-login-keycard-pairing))
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/manage-keys-and-storage)
|
||||
:accessibility-label :enter-seed-phrase-button
|
||||
:icon :main-icons/key
|
||||
:on-press #(hide-sheet-and-dispatch [::multiaccounts.key-storage/key-and-storage-management-pressed])}])
|
||||
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/recover-with-keycard)
|
||||
:accessibility-label :recover-with-keycard-button
|
||||
:icon [react/view {:border-width 1
|
||||
:border-radius 20
|
||||
:border-color colors/blue-light
|
||||
:background-color colors/blue-light
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:width 40
|
||||
:height 40}
|
||||
[react/image {:source (resources/get-image :keycard-logo-blue)
|
||||
:style {:width 24 :height 24}}]]
|
||||
:on-press #(hide-sheet-and-dispatch [::keycard/recover-with-keycard-pressed])}])]])
|
||||
:title (i18n/label :t/recover-with-seed-phrase)
|
||||
:accessibility-label :enter-seed-phrase-button
|
||||
:icon :main-icons/text
|
||||
:on-press #(hide-sheet-and-dispatch [::multiaccounts.recover/enter-phrase-pressed])}]
|
||||
(when (or platform/android?
|
||||
config/keycard-test-menu-enabled?)
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/recover-with-keycard)
|
||||
:accessibility-label :recover-with-keycard-button
|
||||
:icon [react/view {:border-width 1
|
||||
:border-radius 20
|
||||
:border-color colors/blue-light
|
||||
:background-color colors/blue-light
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:width 40
|
||||
:height 40}
|
||||
[react/image {:source (resources/get-image :keycard-logo-blue)
|
||||
:style {:width 24 :height 24}}]]
|
||||
:on-press #(hide-sheet-and-dispatch [::keycard/recover-with-keycard-pressed])}])]]))
|
||||
|
||||
(def bottom-sheet
|
||||
{:content bottom-sheet-view})
|
||||
|
||||
(comment
|
||||
;; Recover with seed to device UI flow
|
||||
(do
|
||||
;; Press get-started on welcome screen
|
||||
(re-frame/dispatch [:multiaccounts.create.ui/intro-wizard])
|
||||
|
||||
;; Goto seed screen
|
||||
(re-frame/dispatch [::multiaccounts.recover/enter-phrase-pressed])
|
||||
|
||||
;; Enter seed phrase for Dim Venerated Yaffle
|
||||
(re-frame/dispatch [:multiaccounts.recover/enter-phrase-input-changed
|
||||
(status-im.utils.security/mask-data "rocket mixed rebel affair umbrella legal resemble scene virus park deposit cargo")])
|
||||
|
||||
;; Recover multiaccount
|
||||
(re-frame/dispatch [:multiaccounts.recover/enter-phrase-next-pressed])
|
||||
|
||||
;; Press Re-encrypt
|
||||
(re-frame/dispatch [:multiaccounts.recover/re-encrypt-pressed])
|
||||
|
||||
;; Press next on default storage (ie store on device)
|
||||
(re-frame/dispatch [:multiaccounts.recover/select-storage-next-pressed])
|
||||
|
||||
;; Enter password (need to wait for a moment for this to finish)
|
||||
(re-frame/dispatch [:multiaccounts.recover/enter-password-next-pressed {:key-code "111111"}])))
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
[status-im.ui.screens.multiaccounts.styles :as styles]
|
||||
[status-im.ui.components.list.views :as list]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.utils.security :as security]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.topbar :as topbar]
|
||||
|
@ -56,3 +57,29 @@
|
|||
{:on-press #(re-frame/dispatch [:multiaccounts.recover.ui/recover-multiaccount-button-pressed])
|
||||
:type :secondary}
|
||||
(i18n/label :t/access-existing-keys)]}]]))
|
||||
|
||||
(defn seed-phrase-input [{:keys [on-change-event
|
||||
seed-word-count
|
||||
seed-shape-invalid?]}]
|
||||
[react/view {:flex 1
|
||||
:justify-content :center
|
||||
:padding-horizontal 16}
|
||||
[quo/text-input
|
||||
{:show-cancel false
|
||||
:auto-correct false
|
||||
:placeholder (i18n/label :t/seed-phrase-placeholder)
|
||||
:monospace true
|
||||
:multiline true
|
||||
:auto-focus true
|
||||
:accessibility-label :passphrase-input
|
||||
:on-change-text #(re-frame/dispatch (conj on-change-event (security/mask-data %)))}]
|
||||
;; word counter view
|
||||
[react/view {:align-items :flex-end}
|
||||
[react/view {:flex-direction :row
|
||||
:align-items :center
|
||||
:padding-vertical 8
|
||||
:opacity (if seed-word-count 1 0)}
|
||||
[quo/text {:color (if seed-shape-invalid? :secondary :main)
|
||||
:size :small}
|
||||
(when-not seed-shape-invalid? "✓ ")
|
||||
(i18n/label-pluralize seed-word-count :t/words-n)]]]])
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[status-im.ui.components.invite.advertiser :as advertiser.invite]
|
||||
[status-im.ui.components.invite.dapp :as dapp.invite]
|
||||
[status-im.ui.screens.multiaccounts.recover.views :as multiaccounts.recover]
|
||||
[status-im.ui.screens.multiaccounts.key-storage.views :as multiaccounts.key-storage]
|
||||
[status-im.ui.screens.signing.views :as signing]
|
||||
[status-im.ui.screens.biometric.views :as biometric]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
|
@ -154,6 +155,15 @@
|
|||
(= :dapp-invite view)
|
||||
[dapp.invite/accept-popover]
|
||||
|
||||
(= :seed-key-uid-mismatch view)
|
||||
[multiaccounts.key-storage/seed-key-uid-mismatch-popover]
|
||||
|
||||
(= :transfer-multiaccount-to-keycard-warning view)
|
||||
[multiaccounts.key-storage/transfer-multiaccount-warning-popover]
|
||||
|
||||
(= :transfer-multiaccount-unknown-error view)
|
||||
[multiaccounts.key-storage/unknown-error-popover]
|
||||
|
||||
:else
|
||||
[view])]]]]])))})))
|
||||
|
||||
|
|
|
@ -55,10 +55,14 @@
|
|||
(fn []
|
||||
(log/debug :on-screen-focus name)
|
||||
(let [on-back-press (fn []
|
||||
(when (and back-handler
|
||||
(not= back-handler :noop))
|
||||
(re-frame/dispatch back-handler))
|
||||
(boolean back-handler))]
|
||||
(if (fn? back-handler)
|
||||
(back-handler)
|
||||
(do
|
||||
(when (and back-handler
|
||||
(vector? back-handler)
|
||||
(not= back-handler :noop))
|
||||
(re-frame/dispatch back-handler))
|
||||
(boolean back-handler))))]
|
||||
(when on-focus (re-frame/dispatch on-focus))
|
||||
(add-back-handler-listener on-back-press)
|
||||
(fn []
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
[status-im.ui.screens.progress.views :as progress]
|
||||
[status-im.ui.screens.multiaccounts.views :as multiaccounts]
|
||||
[status-im.ui.screens.intro.views :as intro]
|
||||
[status-im.keycard.core :as keycard.core]
|
||||
[status-im.ui.screens.keycard.onboarding.views :as keycard.onboarding]
|
||||
[status-im.ui.screens.keycard.recovery.views :as keycard.recovery]
|
||||
[status-im.ui.screens.keycard.views :as keycard]
|
||||
|
@ -61,7 +62,7 @@
|
|||
:back-handler :noop
|
||||
:component intro/wizard-recovery-success}
|
||||
{:name :keycard-onboarding-intro
|
||||
:back-handler :noop
|
||||
:back-handler keycard.core/onboarding-intro-back-handler
|
||||
:component keycard.onboarding/intro}
|
||||
{:name :keycard-onboarding-puk-code
|
||||
:back-handler :noop
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
(ns status-im.ui.screens.routing.key-storage-stack
|
||||
"Manage flow required to change key-storage location"
|
||||
(:require [status-im.ui.screens.routing.core :as navigation]
|
||||
[status-im.ui.screens.multiaccounts.key-storage.views :as key-storage.views]))
|
||||
|
||||
(defonce stack (navigation/create-stack))
|
||||
|
||||
(defn key-storage-stack []
|
||||
[stack {:initial-route-name :actions-not-logged-in
|
||||
:header-mode :none}
|
||||
[{:name :actions-not-logged-in
|
||||
:component key-storage.views/actions-not-logged-in}
|
||||
{:name :actions-logged-in
|
||||
:component key-storage.views/actions-logged-in}
|
||||
{:name :seed-phrase
|
||||
:component key-storage.views/seed-phrase}
|
||||
{:name :storage
|
||||
:component key-storage.views/storage}]])
|
|
@ -11,6 +11,7 @@
|
|||
[status-im.ui.screens.routing.intro-login-stack :as intro-login-stack]
|
||||
[status-im.ui.screens.routing.chat-stack :as chat-stack]
|
||||
[status-im.ui.screens.routing.wallet-stack :as wallet-stack]
|
||||
[status-im.ui.screens.routing.key-storage-stack :as key-storage-stack]
|
||||
[status-im.ui.screens.group.views :as group-chat]
|
||||
[status-im.ui.screens.group.events :as group.events]
|
||||
[status-im.ui.screens.routing.profile-stack :as profile-stack]
|
||||
|
@ -153,7 +154,10 @@
|
|||
{:name :profile
|
||||
:transition :presentation-ios
|
||||
:insets {:bottom true}
|
||||
:component contact/profile}]
|
||||
:component contact/profile}
|
||||
{:name :key-storage-stack
|
||||
:component key-storage-stack/key-storage-stack}]
|
||||
|
||||
(when config/quo-preview-enabled?
|
||||
[{:name :quo-preview
|
||||
:insets {:top false :bottom false}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
[status-im.wallet.core :as wallet]
|
||||
[clojure.string :as string]
|
||||
[status-im.utils.security :as security]
|
||||
[status-im.multiaccounts.recover.core :as recover]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.ethereum.mnemonic :as mnemonic]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.wallet.prices :as prices]
|
||||
|
@ -151,10 +151,10 @@
|
|||
|
||||
(fx/defn import-new-account-seed
|
||||
[{:keys [db]} passphrase hashed-password]
|
||||
{:db (assoc-in db [:add-account :step] :generating)
|
||||
::recover/validate-mnemonic [(security/safe-unmask-data passphrase)
|
||||
#(re-frame/dispatch [:wallet.accounts/seed-validated
|
||||
% passphrase hashed-password])]})
|
||||
{:db (assoc-in db [:add-account :step] :generating)
|
||||
::multiaccounts/validate-mnemonic [(security/safe-unmask-data passphrase)
|
||||
#(re-frame/dispatch [:wallet.accounts/seed-validated
|
||||
% passphrase hashed-password])]})
|
||||
|
||||
(fx/defn new-account-seed-validated
|
||||
{:events [:wallet.accounts/seed-validated]}
|
||||
|
|
|
@ -737,6 +737,7 @@
|
|||
"main-wallet": "Main Wallet",
|
||||
"mainnet-network": "Main network",
|
||||
"make-admin": "Make admin",
|
||||
"manage-keys-and-storage": "Manage keys and storage",
|
||||
"mark-all-read": "Mark all read",
|
||||
"members": {
|
||||
"one": "1 member",
|
||||
|
@ -1033,7 +1034,7 @@
|
|||
"show-qr": "Show QR code",
|
||||
"show-transaction-data": "Show transaction data",
|
||||
"sign-and-send": "Sign and send",
|
||||
"sign-in": "Unlock",
|
||||
"sign-in": "Sign in",
|
||||
"sign-message": "Sign Message",
|
||||
"sign-out": "Sign out",
|
||||
"sign-with": "Sign with",
|
||||
|
@ -1373,5 +1374,30 @@
|
|||
"connect-wallet": "Connect wallet",
|
||||
"open-chat": "Open chat",
|
||||
"favourite-description": "Your favourite websites will appear here",
|
||||
"transfers-fetching-failure": "Transfers history could not be updated. Check your connection and pull down to try again"
|
||||
"transfers-fetching-failure": "Transfers history could not be updated. Check your connection and pull down to try again",
|
||||
"move-and-reset": "Move and Reset",
|
||||
"move-keystore-file-to-keycard": "Move keystore file to keycard?",
|
||||
"database-reset-warning": "Database will be reset. Chats, contacts and settings will be deleted",
|
||||
"empty-keycard-required": "Requires an empty Keycard",
|
||||
"current": "Current",
|
||||
"choose-storage": "Choose storage",
|
||||
"choose-new-location-for-keystore": "Choose a new location to save your keystore file",
|
||||
"get-a-keycard": "Get a Keycard",
|
||||
"keycard-upsell-subtitle": "Your portable, easy to use hardware wallet",
|
||||
"actions": "Actions",
|
||||
"move-keystore-file": "Move keystore file",
|
||||
"select-new-location-for-keys": "Select a new location to save your private key(s)",
|
||||
"reset-database": "Reset database",
|
||||
"reset-database-warning": "Delete chats, contacts and settings. Required when you’ve lost your password",
|
||||
"key-managment": "Key management",
|
||||
"choose-actions": "Choose actions",
|
||||
"master-account": "Master account",
|
||||
"back-up": "Back up",
|
||||
"key-on-device": "Private key is saved on this device",
|
||||
"seed-key-uid-mismatch": "Seed doesn't match",
|
||||
"seed-key-uid-mismatch-desc-1": "The seed phrase you entered does not match {{multiaccount-name}}",
|
||||
"seed-key-uid-mismatch-desc-2": "To manage keys for this account verify your seed phrase and try again.",
|
||||
"recover-with-seed-phrase": "Recover with seed phrase",
|
||||
"transfer-ma-unknown-error-desc-1": "It looks like your multiaccount was not deleted. Database may have been reset",
|
||||
"transfer-ma-unknown-error-desc-2": "Please check your account list and try again. If the account is not listed go to Access existing keys to recover with seed phrase"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue