diff --git a/resources/images/icons/key@2x.png b/resources/images/icons/key@2x.png new file mode 100644 index 0000000000..411c4ab353 Binary files /dev/null and b/resources/images/icons/key@2x.png differ diff --git a/resources/images/icons/key@3x.png b/resources/images/icons/key@3x.png new file mode 100644 index 0000000000..21cb98d3fc Binary files /dev/null and b/resources/images/icons/key@3x.png differ diff --git a/src/status_im/keycard/core.cljs b/src/status_im/keycard/core.cljs index d284310f77..6876d110a9 100644 --- a/src/status_im/keycard/core.cljs +++ b/src/status_im/keycard/core.cljs @@ -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)) diff --git a/src/status_im/multiaccounts/core.cljs b/src/status_im/multiaccounts/core.cljs index 4bb2f3d65f..72a0001fa2 100644 --- a/src/status_im/multiaccounts/core.cljs +++ b/src/status_im/multiaccounts/core.cljs @@ -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)) diff --git a/src/status_im/multiaccounts/key_storage/core.cljs b/src/status_im/multiaccounts/key_storage/core.cljs new file mode 100644 index 0000000000..b0e0cc2703 --- /dev/null +++ b/src/status_im/multiaccounts/key_storage/core.cljs @@ -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)))) diff --git a/src/status_im/multiaccounts/key_storage/core_test.cljs b/src/status_im/multiaccounts/key_storage/core_test.cljs new file mode 100644 index 0000000000..e67bdb8586 --- /dev/null +++ b/src/status_im/multiaccounts/key_storage/core_test.cljs @@ -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))) + diff --git a/src/status_im/multiaccounts/recover/core.cljs b/src/status_im/multiaccounts/recover/core.cljs index 4ff2d2f3db..9e9661ba96 100644 --- a/src/status_im/multiaccounts/recover/core.cljs +++ b/src/status_im/multiaccounts/recover/core.cljs @@ -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 diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 02f5346f23..092ffd6f3f 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -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] diff --git a/src/status_im/subs_test.cljs b/src/status_im/subs_test.cljs index dca3aedd3a..e0e2242413 100644 --- a/src/status_im/subs_test.cljs +++ b/src/status_im/subs_test.cljs @@ -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"))))) diff --git a/src/status_im/ui/components/accordion.cljs b/src/status_im/ui/components/accordion.cljs new file mode 100644 index 0000000000..b6274892ae --- /dev/null +++ b/src/status_im/ui/components/accordion.cljs @@ -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 + ) diff --git a/src/status_im/ui/screens/intro/views.cljs b/src/status_im/ui/screens/intro/views.cljs index 415357550e..833c5fe31f 100644 --- a/src/status_im/ui/screens/intro/views.cljs +++ b/src/status_im/ui/screens/intro/views.cljs @@ -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 [] diff --git a/src/status_im/ui/screens/keycard/onboarding/views.cljs b/src/status_im/ui/screens/keycard/onboarding/views.cljs index 345747b549..7bf2a7c6f0 100644 --- a/src/status_im/ui/screens/keycard/onboarding/views.cljs +++ b/src/status_im/ui/screens/keycard/onboarding/views.cljs @@ -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 diff --git a/src/status_im/ui/screens/multiaccounts/key_storage/styles.cljs b/src/status_im/ui/screens/multiaccounts/key_storage/styles.cljs new file mode 100644 index 0000000000..010accbf88 --- /dev/null +++ b/src/status_im/ui/screens/multiaccounts/key_storage/styles.cljs @@ -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}) diff --git a/src/status_im/ui/screens/multiaccounts/key_storage/views.cljs b/src/status_im/ui/screens/multiaccounts/key_storage/views.cljs new file mode 100644 index 0000000000..9a2da716a7 --- /dev/null +++ b/src/status_im/ui/screens/multiaccounts/key_storage/views.cljs @@ -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]))) diff --git a/src/status_im/ui/screens/multiaccounts/login/views.cljs b/src/status_im/ui/screens/multiaccounts/login/views.cljs index 699269c9f5..0a226b11e6 100644 --- a/src/status_im/ui/screens/multiaccounts/login/views.cljs +++ b/src/status_im/ui/screens/multiaccounts/login/views.cljs @@ -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)]]}]])) diff --git a/src/status_im/ui/screens/multiaccounts/recover/views.cljs b/src/status_im/ui/screens/multiaccounts/recover/views.cljs index 19f91d80c4..faf486a03c 100644 --- a/src/status_im/ui/screens/multiaccounts/recover/views.cljs +++ b/src/status_im/ui/screens/multiaccounts/recover/views.cljs @@ -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"}]))) diff --git a/src/status_im/ui/screens/multiaccounts/views.cljs b/src/status_im/ui/screens/multiaccounts/views.cljs index 1963a1c681..9025e909e4 100644 --- a/src/status_im/ui/screens/multiaccounts/views.cljs +++ b/src/status_im/ui/screens/multiaccounts/views.cljs @@ -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)]]]]) diff --git a/src/status_im/ui/screens/popover/views.cljs b/src/status_im/ui/screens/popover/views.cljs index 7f69af20a4..f8162df03a 100644 --- a/src/status_im/ui/screens/popover/views.cljs +++ b/src/status_im/ui/screens/popover/views.cljs @@ -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])]]]]])))}))) diff --git a/src/status_im/ui/screens/routing/core.cljs b/src/status_im/ui/screens/routing/core.cljs index 8f7181ad4b..9d7bfe9b03 100644 --- a/src/status_im/ui/screens/routing/core.cljs +++ b/src/status_im/ui/screens/routing/core.cljs @@ -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 [] diff --git a/src/status_im/ui/screens/routing/intro_login_stack.cljs b/src/status_im/ui/screens/routing/intro_login_stack.cljs index 2eeadd4cab..fa934c4036 100644 --- a/src/status_im/ui/screens/routing/intro_login_stack.cljs +++ b/src/status_im/ui/screens/routing/intro_login_stack.cljs @@ -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 diff --git a/src/status_im/ui/screens/routing/key_storage_stack.cljs b/src/status_im/ui/screens/routing/key_storage_stack.cljs new file mode 100644 index 0000000000..e7fce55afa --- /dev/null +++ b/src/status_im/ui/screens/routing/key_storage_stack.cljs @@ -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}]]) diff --git a/src/status_im/ui/screens/routing/main.cljs b/src/status_im/ui/screens/routing/main.cljs index 0c7f54375c..64e4c21a39 100644 --- a/src/status_im/ui/screens/routing/main.cljs +++ b/src/status_im/ui/screens/routing/main.cljs @@ -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} diff --git a/src/status_im/wallet/accounts/core.cljs b/src/status_im/wallet/accounts/core.cljs index 82b3c9b448..66da9f057a 100644 --- a/src/status_im/wallet/accounts/core.cljs +++ b/src/status_im/wallet/accounts/core.cljs @@ -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]} diff --git a/translations/en.json b/translations/en.json index da5988a92a..349a2edc73 100644 --- a/translations/en.json +++ b/translations/en.json @@ -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" }