feat!: add key pairs and accounts settings to wallet settings (#20464)

* tweak: use support blur colors for action drawer icons

* tweak: support blur colors for drawer-top icon

* tweak: support blur colors for account list inside keypair component

* tweak: use blur colors for missing-keypair bottom sheet actions

* feat: remove feature-flag for key-pairs and accounts inside wallet settings

* fix: ensure key-pairs-and-accounts settings are visible

* tweak: display key-pair rename screen as full-screen modal

* tweak: animate key-pair scanning screen as modal

* fix: do not allow user to rename key-pair to an existing key-pair name

* fix: ensure we visualise the validation error for key-pair name that is too long

* fix: ensure we can see the recovery-phrase suggestions when testnet-mode is active

* tweak: show recovery-phrase errors when keyboard is hidden

* fix: ensure we do not re-hash password

* fix: ensure we call `on-close` function when successfully handling biometrics auth

* fix: ensure we clear error states when editing the recovery-phrase input

* fix: use blur styles for standard authentication password input

* chore: add labels for qr validation

* tweak: handle display import-qr error and allow for re-scanning

* tweak: update blur background for all bottom-sheets

* chore: add feature-flag for import-all key-pairs button for missing key-pairs

* tidy: remove unused variable

* fix: ensure layout for android is correct when entering seed-phrase in testnet-mode

* fix: ensure we return the updated db when updating the db with backup key-pair

* tweak: coerce nil to empty string

* tweak: change validation error message for short key-pair name

* tweak: hide the options icon for the default key-pair inside key-pairs and accounts settings
This commit is contained in:
Sean Hagstrom 2024-06-26 13:16:00 +01:00 committed by GitHub
parent 6931fd0052
commit 256f3f8f24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 245 additions and 177 deletions

View File

@ -8,10 +8,11 @@
[react-native.core :as rn])) [react-native.core :as rn]))
(defn- get-icon-color (defn- get-icon-color
[danger? theme] [blur? danger? theme]
(if danger? (cond
(colors/theme-colors colors/danger-50 colors/danger-60 theme) danger? (colors/theme-colors colors/danger-50 colors/danger-60 theme)
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme))) blur? colors/white-opa-70
:else (colors/theme-colors colors/neutral-50 colors/neutral-40 theme)))
(defn- divider (defn- divider
[theme blur?] [theme blur?]
@ -51,7 +52,7 @@
:accessible true :accessible true
:style (style/left-icon sub-label)} :style (style/left-icon sub-label)}
[icon/icon icon [icon/icon icon
{:color (or icon-color (get-icon-color danger? theme)) {:color (or icon-color (get-icon-color blur? danger? theme))
:no-color no-icon-color? :no-color no-icon-color?
:size 20}]]) :size 20}]])
[rn/view [rn/view
@ -84,7 +85,7 @@
:accessible true :accessible true
:accessibility-label :right-icon-for-action} :accessibility-label :right-icon-for-action}
[icon/icon right-icon [icon/icon right-icon
{:color (get-icon-color danger? theme) {:color (get-icon-color blur? danger? theme)
:size 20}]]) :size 20}]])
(when (= state :selected) (when (= state :selected)
[rn/view {:style style/right-icon} [rn/view {:style style/right-icon}

View File

@ -18,7 +18,7 @@
(defn- left-image (defn- left-image
[{:keys [type customization-color account-avatar-emoji account-avatar-type icon-avatar [{:keys [type customization-color account-avatar-emoji account-avatar-type icon-avatar
profile-picture]}] profile-picture blur?]}]
(case type (case type
:account [account-avatar/view :account [account-avatar/view
{:customization-color customization-color {:customization-color customization-color
@ -28,6 +28,7 @@
:keypair [icon-avatar/icon-avatar :keypair [icon-avatar/icon-avatar
{:icon icon-avatar {:icon icon-avatar
:border? true :border? true
:blur? blur?
:color :neutral}] :color :neutral}]
:default-keypair [user-avatar/user-avatar :default-keypair [user-avatar/user-avatar
@ -213,6 +214,7 @@
[rn/view {:style style/left-container} [rn/view {:style style/left-container}
[left-image [left-image
{:type type {:type type
:blur? blur?
:customization-color customization-color :customization-color customization-color
:account-avatar-emoji account-avatar-emoji :account-avatar-emoji account-avatar-emoji
:account-avatar-type account-avatar-type :account-avatar-type account-avatar-type

View File

@ -31,7 +31,8 @@
:size :paragraph-2} :size :paragraph-2}
(:name account-props)] (:name account-props)]
[address-text/view [address-text/view
{:networks networks {:blur? blur?
:networks networks
:address (:address account-props) :address (:address account-props)
:format :short}]]] :format :short}]]]
(when (= action :icon) (when (= action :icon)

View File

@ -47,12 +47,13 @@
:accessibility-label :title} :accessibility-label :title}
[text/text {:weight :semi-bold} [text/text {:weight :semi-bold}
(if (= type :default-keypair) (keypair-string full-name) full-name)] (if (= type :default-keypair) (keypair-string full-name) full-name)]
(if (= action :selector) (case action
[selectors/view :none nil
{:type :radio :selector [selectors/view
:checked? selected? {:type :radio
:blur? blur? :checked? selected?
:customization-color customization-color}] :blur? blur?
:customization-color customization-color}]
[rn/pressable {:on-press on-options-press} [rn/pressable {:on-press on-options-press}
[icon/icon :i/options [icon/icon :i/options
{:color (if blur? {:color (if blur?

View File

@ -11,7 +11,7 @@
[utils.i18n :as i18n])) [utils.i18n :as i18n]))
(defn title-view (defn title-view
[{:keys [keypairs blur? on-import-press]}] [{:keys [keypairs blur? on-import-press show-import-all?]}]
(let [theme (quo.theme/use-theme)] (let [theme (quo.theme/use-theme)]
[rn/view [rn/view
{:accessibility-label :title {:accessibility-label :title
@ -29,12 +29,13 @@
:style {:color colors/warning-60}} :style {:color colors/warning-60}}
(i18n/label :t/amount-missing-keypairs (i18n/label :t/amount-missing-keypairs
{:amount (str (count keypairs))})] {:amount (str (count keypairs))})]
[button/button (when show-import-all?
{:type :outline [button/button
:background :blur {:type :outline
:size 24 :background :blur
:on-press on-import-press} :size 24
(i18n/label :t/import)]] :on-press on-import-press}
(i18n/label :t/import)])]
[text/text [text/text
{:size :paragraph-2 {:size :paragraph-2
:style (style/subtitle blur? theme)} :style (style/subtitle blur? theme)}

View File

@ -1,7 +1,6 @@
(ns status-im.common.bottom-sheet.style (ns status-im.common.bottom-sheet.style
(:require (:require
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]))
[react-native.platform :as platform]))
(def ^:private sheet-border-radius 20) (def ^:private sheet-border-radius 20)
@ -22,11 +21,8 @@
:left 0 :left 0
:right 0}) :right 0})
(defn shell-bg (def shell-bg
[blur-background] {:background-color colors/bottom-sheet-background-blur
{:background-color (if blur-background
blur-background
(if platform/ios? colors/white-opa-5 colors/neutral-100-opa-90))
:flex 1}) :flex 1})
(def shell-bg-container (def shell-bg-container

View File

@ -63,7 +63,7 @@
(defn view (defn view
[{:keys [hide? insets]} [{:keys [hide? insets]}
{:keys [content selected-item padding-bottom-override border-radius on-close shell? {:keys [content selected-item padding-bottom-override border-radius on-close shell?
gradient-cover? customization-color hide-handle? blur-radius blur-background] gradient-cover? customization-color hide-handle? blur-radius]
:or {border-radius 12}}] :or {border-radius 12}}]
(let [theme (quo.theme/use-theme) (let [theme (quo.theme/use-theme)
{window-height :height} (rn/get-window) {window-height :height} (rn/get-window)
@ -134,7 +134,7 @@
(when shell? (when shell?
[rn/view {:style style/shell-bg-container} [rn/view {:style style/shell-bg-container}
[quo/blur [quo/blur
{:style (style/shell-bg blur-background) {:style style/shell-bg
:blur-radius (or blur-radius 20) :blur-radius (or blur-radius 20)
:blur-amount 32 :blur-amount 32
:blur-type :transparent :blur-type :transparent

View File

@ -1,6 +1,5 @@
(ns status-im.common.enter-seed-phrase.style (ns status-im.common.enter-seed-phrase.style
(:require (:require [react-native.platform :as platform]))
[react-native.safe-area :as safe-area]))
(def full-layout {:flex 1}) (def full-layout {:flex 1})
@ -11,6 +10,13 @@
:left 0 :left 0
:right 0}) :right 0})
(defn recovery-phrase-container
[{:keys [banner-offset insets keyboard-shown?]}]
{:flex 1
:padding-bottom (if keyboard-shown?
(when platform/ios? banner-offset)
(:bottom insets))})
(def form-container (def form-container
{:flex 1 {:flex 1
:padding-horizontal 20 :padding-horizontal 20
@ -29,9 +35,7 @@
:margin-top 12 :margin-top 12
:margin-horizontal -20}) :margin-horizontal -20})
(defn continue-button (def continue-button
[keyboard-shown?] {:margin-top :auto})
{:margin-top :auto
:margin-bottom (when-not keyboard-shown? (safe-area/get-bottom))})
(def keyboard-container {:margin-top :auto}) (def keyboard-container {:margin-top :auto})

View File

@ -96,7 +96,7 @@
(take 7))) (take 7)))
(defn recovery-phrase-screen (defn recovery-phrase-screen
[{:keys [keypair title recovering-keypair? render-controls]}] [{:keys [banner-offset initial-insets keypair title recovering-keypair? render-controls]}]
(reagent/with-let [keyboard-shown? (reagent/atom false) (reagent/with-let [keyboard-shown? (reagent/atom false)
keyboard-show-listener (.addListener rn/keyboard keyboard-show-listener (.addListener rn/keyboard
"keyboardDidShow" "keyboardDidShow"
@ -114,7 +114,8 @@
seed-phrase (reagent/atom "") seed-phrase (reagent/atom "")
on-change-seed-phrase (fn [new-phrase] on-change-seed-phrase (fn [new-phrase]
(when @invalid-seed-phrase? (when @invalid-seed-phrase?
(reset! invalid-seed-phrase? false) (reset! invalid-seed-phrase? false))
(when @incorrect-seed-phrase?
(reset! incorrect-seed-phrase? false)) (reset! incorrect-seed-phrase? false))
(reset! seed-phrase new-phrase)) (reset! seed-phrase new-phrase))
on-submit (fn [] on-submit (fn []
@ -161,7 +162,10 @@
button-disabled? (or error-state? button-disabled? (or error-state?
(not (constants/seed-phrase-valid-length word-count)) (not (constants/seed-phrase-valid-length word-count))
(not all-words-valid?))] (not all-words-valid?))]
[:<> [rn/view
{:style (style/recovery-phrase-container {:insets initial-insets
:banner-offset banner-offset
:keyboard-shown? @keyboard-shown?})}
[recovery-phrase-form [recovery-phrase-form
{:title title {:title title
:keypair keypair :keypair keypair
@ -172,18 +176,18 @@
(if (fn? render-controls) (if (fn? render-controls)
(render-controls {:submit-disabled? button-disabled? (render-controls {:submit-disabled? button-disabled?
:keyboard-shown? @keyboard-shown? :keyboard-shown? @keyboard-shown?
:container-style (style/continue-button @keyboard-shown?) :container-style style/continue-button
:prepare-seed-phrase secure-clean-seed-phrase :prepare-seed-phrase secure-clean-seed-phrase
:focus-input focus-input :focus-input focus-input
:seed-phrase (security/mask-data @seed-phrase) :seed-phrase (security/mask-data @seed-phrase)
:set-incorrect-seed-phrase set-incorrect-seed-phrase}) :set-incorrect-seed-phrase set-incorrect-seed-phrase})
[quo/button [quo/button
{:container-style (style/continue-button @keyboard-shown?) {:container-style style/continue-button
:type :primary :type :primary
:disabled? button-disabled? :disabled? button-disabled?
:on-press on-submit} :on-press on-submit}
(i18n/label :t/continue)])] (i18n/label :t/continue)])]
(when @keyboard-shown? (when (or @keyboard-shown? error-state?)
[rn/view {:style style/keyboard-container} [rn/view {:style style/keyboard-container}
[quo/predictive-keyboard [quo/predictive-keyboard
{:type suggestions-state {:type suggestions-state
@ -197,7 +201,8 @@
(defn screen (defn screen
[{:keys [initial-insets title keypair navigation-icon recovering-keypair? render-controls]}] [{:keys [initial-insets title keypair navigation-icon recovering-keypair? render-controls]}]
(let [{navigation-bar-top :top} initial-insets] (let [{navigation-bar-top :top} initial-insets
banner-offset (rf/sub [:alert-banners/top-margin])]
[rn/view {:style style/full-layout} [rn/view {:style style/full-layout}
[rn/keyboard-avoiding-view {:style style/page-container} [rn/keyboard-avoiding-view {:style style/page-container}
[quo/page-nav [quo/page-nav
@ -210,6 +215,8 @@
{:title title {:title title
:keypair keypair :keypair keypair
:render-controls render-controls :render-controls render-controls
:banner-offset banner-offset
:initial-insets initial-insets
:recovering-keypair? recovering-keypair?}]]])) :recovering-keypair? recovering-keypair?}]]]))
(defn view (defn view

View File

@ -195,7 +195,7 @@
true) true)
(defn view (defn view
[{:keys [title subtitle validate-fn on-success-scan error-message share-button?]}] [{:keys [title subtitle validate-fn on-success-scan error-message share-button? import-keypair?]}]
(let [insets (safe-area/get-insets) (let [insets (safe-area/get-insets)
qr-code-succeed? (reagent/atom false) qr-code-succeed? (reagent/atom false)
qr-view-finder (reagent/atom {}) qr-view-finder (reagent/atom {})
@ -233,7 +233,9 @@
:set-qr-code-succeeded (fn [value] :set-qr-code-succeeded (fn [value]
(when on-success-scan (when on-success-scan
(on-success-scan value)) (on-success-scan value))
(rf/dispatch [:navigate-back])) (if import-keypair?
(set-rescan-timeout)
(rf/dispatch [:navigate-back])))
:set-rescan-timeout set-rescan-timeout}]) :set-rescan-timeout set-rescan-timeout}])
[rn/view {:style (style/root-container (:top insets))} [rn/view {:style (style/root-container (:top insets))}
[header [header

View File

@ -33,6 +33,7 @@
:size 24}]] :size 24}]]
[password-input/view [password-input/view
{:on-press-biometrics on-press-biometrics {:on-press-biometrics on-press-biometrics
:blur? true
:processing processing :processing processing
:error error :error error
:default-password password :default-password password

View File

@ -20,7 +20,7 @@
(rf/reg-event-fx :standard-auth/authorize authorize) (rf/reg-event-fx :standard-auth/authorize authorize)
(defn authorize-with-biometric (defn authorize-with-biometric
[_ [{:keys [on-auth-success on-auth-fail] :as args}]] [_ [{:keys [on-auth-success on-auth-fail on-close] :as args}]]
(let [args-with-biometric-btn (let [args-with-biometric-btn
(assoc args (assoc args
:on-press-biometric :on-press-biometric
@ -31,7 +31,10 @@
{:prompt-message (i18n/label :t/biometric-auth-confirm-message) {:prompt-message (i18n/label :t/biometric-auth-confirm-message)
:on-cancel #(rf/dispatch [:standard-auth/authorize-with-password :on-cancel #(rf/dispatch [:standard-auth/authorize-with-password
args-with-biometric-btn]) args-with-biometric-btn])
:on-success #(rf/dispatch [:standard-auth/on-biometric-success on-auth-success]) :on-success (fn []
(when (fn? on-close)
(on-close))
(rf/dispatch [:standard-auth/on-biometric-success on-auth-success]))
:on-fail (fn [err] :on-fail (fn [err]
(rf/dispatch [:standard-auth/authorize-with-password (rf/dispatch [:standard-auth/authorize-with-password
args-with-biometric-btn]) args-with-biometric-btn])

View File

@ -14,10 +14,12 @@
(> (-> s str string/trim count) constants/key-pair-name-max-length)) (> (-> s str string/trim count) constants/key-pair-name-max-length))
(defn validation-keypair-name (defn validation-keypair-name
[s] [s existing-keypair-names]
(cond (cond
(string/blank? s) nil (string/blank? s) nil
(validators/has-emojis? s) (i18n/label :t/key-name-error-emoji) (validators/has-emojis? s) (i18n/label :t/key-name-error-emoji)
(validators/has-special-characters? s) (i18n/label :t/key-name-error-special-char) (validators/has-special-characters? s) (i18n/label :t/key-name-error-special-char)
(keypair-too-short? s) (i18n/label :t/your-key-pair-name-is-too-short) (keypair-too-short? s) (i18n/label :t/key-name-error-too-short
(keypair-too-long? s) (i18n/label :t/your-key-pair-name-is-too-long))) {:count constants/key-pair-name-min-length})
(keypair-too-long? s) (i18n/label :t/your-key-pair-name-is-too-long)
(contains? existing-keypair-names s) (i18n/label :t/key-name-error-taken)))

View File

@ -2,6 +2,7 @@
(:require (:require
[cljs.test :refer-macros [deftest are]] [cljs.test :refer-macros [deftest are]]
[status-im.common.validation.keypair :as keypair-validator] [status-im.common.validation.keypair :as keypair-validator]
[status-im.constants :as constants]
[utils.i18n :as i18n])) [utils.i18n :as i18n]))
(deftest keypair-name-too-short-test (deftest keypair-name-too-short-test
@ -18,10 +19,12 @@
(deftest validation-keypair-name-test (deftest validation-keypair-name-test
(are [arg expected] (are [arg expected]
(= (keypair-validator/validation-keypair-name arg) expected) (= (keypair-validator/validation-keypair-name arg #{"Collection"}) expected)
nil nil nil nil
"" nil "" nil
"name !" (i18n/label :t/key-name-error-special-char) "name !" (i18n/label :t/key-name-error-special-char)
"Hello 😊" (i18n/label :t/key-name-error-emoji) "Hello 😊" (i18n/label :t/key-name-error-emoji)
"abc" (i18n/label :t/your-key-pair-name-is-too-short) "abc" (i18n/label :t/key-name-error-too-short
{:count constants/key-pair-name-min-length})
"Collection" (i18n/label :t/key-name-error-taken)
(apply str (repeat 25 "a")) (i18n/label :t/your-key-pair-name-is-too-long))) (apply str (repeat 25 "a")) (i18n/label :t/your-key-pair-name-is-too-long)))

View File

@ -92,23 +92,28 @@
(rf/reg-event-fx :wallet/make-keypairs-accounts-fully-operable make-keypairs-accounts-fully-operable) (rf/reg-event-fx :wallet/make-keypairs-accounts-fully-operable make-keypairs-accounts-fully-operable)
(defn connection-string-for-import-keypair (defn connection-string-for-import-keypair
[{:keys [db]} [{:keys [sha3-pwd keypairs-key-uids connection-string]}]] [{:keys [db]} [{:keys [sha3-pwd keypairs-key-uids connection-string on-success]}]]
(let [key-uid (get-in db [:profile/profile :key-uid])] (let [key-uid (get-in db [:profile/profile :key-uid])]
{:fx [[:effects.syncing/import-keypairs-keystores {:fx [[:effects.syncing/import-keypairs-keystores
{:key-uid key-uid {:key-uid key-uid
:sha3-pwd sha3-pwd :sha3-pwd sha3-pwd
:keypairs-key-uids keypairs-key-uids :keypairs-key-uids keypairs-key-uids
:connection-string connection-string :connection-string connection-string
:on-success #(rf/dispatch [:wallet/make-keypairs-accounts-fully-operable %]) :on-success (fn [key-uids]
:on-fail #(rf/dispatch [:toasts/upsert (rf/call-continuation on-success)
{:type :negative (rf/dispatch [:wallet/make-keypairs-accounts-fully-operable key-uids]))
:theme :dark :on-fail (fn [error]
:text %}])}]]})) (log/error "failed to import missing key pairs with connection string"
{:error error})
(rf/dispatch [:toasts/upsert
{:type :negative
:theme :dark
:text (i18n/label :t/incorrect-qr-code)}]))}]]}))
(rf/reg-event-fx :wallet/connection-string-for-import-keypair connection-string-for-import-keypair) (rf/reg-event-fx :wallet/connection-string-for-import-keypair connection-string-for-import-keypair)
(defn success-keypair-qr-scan (defn success-keypair-qr-scan
[_ [connection-string keypairs-key-uids]] [_ [connection-string keypairs-key-uids on-import-success]]
{:fx [[:dispatch {:fx [[:dispatch
[:standard-auth/authorize-with-password [:standard-auth/authorize-with-password
{:blur? true {:blur? true
@ -120,7 +125,8 @@
[:wallet/connection-string-for-import-keypair [:wallet/connection-string-for-import-keypair
{:connection-string connection-string {:connection-string connection-string
:keypairs-key-uids keypairs-key-uids :keypairs-key-uids keypairs-key-uids
:sha3-pwd password}]))}]]]}) :sha3-pwd password
:on-success on-import-success}]))}]]]})
(rf/reg-event-fx :wallet/success-keypair-qr-scan success-keypair-qr-scan) (rf/reg-event-fx :wallet/success-keypair-qr-scan success-keypair-qr-scan)
@ -146,7 +152,7 @@
{:fx [[:json-rpc/call {:fx [[:json-rpc/call
[{:method "accounts_makePrivateKeyKeypairFullyOperable" [{:method "accounts_makePrivateKeyKeypairFullyOperable"
:params [(security/safe-unmask-data private-key) :params [(security/safe-unmask-data private-key)
(-> password security/safe-unmask-data native-module/sha3)] (security/safe-unmask-data password)]
:on-success on-success :on-success on-success
:on-error on-error}]]]}) :on-error on-error}]]]})

View File

@ -41,31 +41,37 @@
[quo/action-drawer [quo/action-drawer
[(when has-paired-device [(when has-paired-device
(if-not missing-keypair? (if-not missing-keypair?
[{:icon :i/qr-code [{:blur? true
:icon :i/qr-code
:accessibility-label :show-key-pr-qr :accessibility-label :show-key-pr-qr
:label (i18n/label :t/show-encrypted-qr-of-key-pairs) :label (i18n/label :t/show-encrypted-qr-of-key-pairs)
:on-press on-show-qr}] :on-press on-show-qr}]
[{:icon :i/scan [{:blur? true
:icon :i/scan
:accessibility-label :import-by-scan-qr :accessibility-label :import-by-scan-qr
:label (i18n/label :t/import-by-scanning-encrypted-qr) :label (i18n/label :t/import-by-scanning-encrypted-qr)
:on-press on-scan-qr}])) :on-press on-scan-qr}]))
(when (= (:type drawer-props) :keypair) (when (= (:type drawer-props) :keypair)
[(when missing-keypair? [(when missing-keypair?
(case (:type keypair) (case (:type keypair)
:seed {:icon :i/seed :seed {:blur? true
:icon :i/seed
:accessibility-label :import-seed-phrase :accessibility-label :import-seed-phrase
:label (i18n/label :t/import-by-entering-recovery-phrase) :label (i18n/label :t/import-by-entering-recovery-phrase)
:on-press on-import-seed-phrase} :on-press on-import-seed-phrase}
:key {:icon :i/key :key {:blur? true
:icon :i/key
:accessibility-label :import-private-key :accessibility-label :import-private-key
:label (i18n/label :t/import-by-entering-private-key) :label (i18n/label :t/import-by-entering-private-key)
:on-press on-import-private-key} :on-press on-import-private-key}
nil)) nil))
{:icon :i/edit {:blur? true
:icon :i/edit
:accessibility-label :rename-key-pair :accessibility-label :rename-key-pair
:label (i18n/label :t/rename-key-pair) :label (i18n/label :t/rename-key-pair)
:on-press on-rename-keypair} :on-press on-rename-keypair}
{:icon :i/delete {:blur? true
:icon :i/delete
:accessibility-label :remove-key-pair :accessibility-label :remove-key-pair
:add-divider? true :add-divider? true
:danger? true :danger? true

View File

@ -11,13 +11,16 @@
[] []
(let [keypairs-key-uids (rf/sub [:get-screen-params]) (let [keypairs-key-uids (rf/sub [:get-screen-params])
on-success-scan (rn/use-callback (fn [scanned-text] on-success-scan (rn/use-callback (fn [scanned-text]
(rf/dispatch [:wallet/success-keypair-qr-scan scanned-text (rf/dispatch [:wallet/success-keypair-qr-scan
keypairs-key-uids]) scanned-text
keypairs-key-uids
[:navigate-back]])
[keypairs-key-uids]))] [keypairs-key-uids]))]
[scan-qr-code/view [scan-qr-code/view
{:title (i18n/label :t/scan-key-pairs-qr-code) {:title (i18n/label :t/scan-key-pairs-qr-code)
:subtitle (i18n/label :t/find-it-in-setting) :subtitle (i18n/label :t/find-it-in-setting)
:share-button? false :share-button? false
:import-keypair? true
:validate-fn sync-utils/valid-connection-string? :validate-fn sync-utils/valid-connection-string?
:error-message (i18n/label :t/invalid-qr) :error-message (i18n/label :t/invalid-key-pair-qr)
:on-success-scan on-success-scan}])) :on-success-scan on-success-scan}]))

View File

@ -4,7 +4,8 @@
{:margin-bottom 8}) {:margin-bottom 8})
(def bottom-action (def bottom-action
{:margin-horizontal -20}) {:margin-horizontal -20
:margin-vertical -12})
(def error-container (def error-container
{:margin-left 20 {:margin-left 20

View File

@ -2,6 +2,7 @@
(:require [clojure.string :as string] (:require [clojure.string :as string]
[quo.core :as quo] [quo.core :as quo]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.floating-button-page.view :as floating-button-page] [status-im.common.floating-button-page.view :as floating-button-page]
[status-im.common.validation.keypair :as keypair-validator] [status-im.common.validation.keypair :as keypair-validator]
[status-im.constants :as constants] [status-im.constants :as constants]
@ -14,19 +15,24 @@
(defn view (defn view
[] []
(let [{:keys [name key-uid]} (rf/sub [:get-screen-params]) (let [insets (safe-area/get-insets)
{existing-keypair-name :name
:keys [key-uid]} (rf/sub [:get-screen-params])
existing-keypair-names (rf/sub [:wallet/keypair-names])
customization-color (rf/sub [:profile/customization-color]) customization-color (rf/sub [:profile/customization-color])
[unsaved-keypair-name set-unsaved-keypair-name] (rn/use-state name) [unsaved-keypair-name set-unsaved-keypair-name] (rn/use-state existing-keypair-name)
[error-msg set-error-msg] (rn/use-state nil) [error-msg set-error-msg] (rn/use-state nil)
[typing? set-typing?] (rn/use-state false) [typing? set-typing?] (rn/use-state false)
validate-keypair-name (rn/use-callback validate-keypair-name (rn/use-callback
(debounce/debounce (debounce/debounce
(fn [name] (fn [input-name]
(set-error-msg (set-error-msg
(keypair-validator/validation-keypair-name (keypair-validator/validation-keypair-name
name)) input-name
existing-keypair-names))
(set-typing? false)) (set-typing? false))
300)) 300)
[existing-keypair-names])
on-change-text (rn/use-callback (fn [text] on-change-text (rn/use-callback (fn [text]
(set-typing? true) (set-typing? true)
(set-unsaved-keypair-name (set-unsaved-keypair-name
@ -40,45 +46,57 @@
:keypair-name :keypair-name
unsaved-keypair-name}]) unsaved-keypair-name}])
[unsaved-keypair-name key-uid])] [unsaved-keypair-name key-uid])]
[floating-button-page/view [quo/overlay {:type :shell}
{:header [quo/page-nav [floating-button-page/view
{:icon-name :i/close {:footer-container-padding 0
:on-press navigate-back :blur? true
:accessibility-label :top-bar}] :header [quo/page-nav
:footer [quo/bottom-actions {:margin-top (:top insets)
{:actions :one-action :icon-name :i/close
:button-one-label (i18n/label :t/save) :background :blur
:button-one-props {:disabled? (or typing? :on-press navigate-back
(string/blank? unsaved-keypair-name) :accessibility-label :top-bar}]
(not (string/blank? error-msg))) :footer [quo/bottom-actions
:customization-color customization-color {:actions :one-action
:on-press on-save} :blur? true
:container-style style/bottom-action}]} :button-one-label (i18n/label :t/save)
[quo/page-top :button-one-props {:blur? true
{:container-style style/header-container :disabled? (or typing?
:title (i18n/label :t/rename-key-pair) (= existing-keypair-name
:description :context-tag unsaved-keypair-name)
:context-tag {:type :icon (string/blank?
:size 24 unsaved-keypair-name)
:context name (not (string/blank?
:icon :i/seed-phrase}}] error-msg)))
[quo/input :customization-color customization-color
{:container-style {:margin-horizontal 20} :on-press on-save}
:placeholder (i18n/label :t/keypair-name-input-placeholder) :container-style style/bottom-action}]}
:label (i18n/label :t/keypair-name) [quo/page-top
:default-value unsaved-keypair-name {:container-style style/header-container
:char-limit constants/key-pair-name-max-length :title (i18n/label :t/rename-key-pair)
:max-length constants/key-pair-name-max-length :description :context-tag
:auto-focus true :blur? true
:clearable? (not (string/blank? unsaved-keypair-name)) :context-tag {:type :icon
:on-clear on-clear :size 24
:on-change-text on-change-text :context existing-keypair-name
:error? (not (string/blank? error-msg))}] :icon :i/seed-phrase}}]
(when-not (string/blank? error-msg) [quo/input
[quo/info-message {:blur? true
{:type :error :container-style {:margin-horizontal 20}
:size :default :placeholder (i18n/label :t/keypair-name-input-placeholder)
:icon :i/info :label (i18n/label :t/keypair-name)
:container-style style/error-container} :default-value unsaved-keypair-name
error-msg])])) :char-limit constants/key-pair-name-max-length
:auto-focus true
:clearable? (not (string/blank? unsaved-keypair-name))
:on-clear on-clear
:on-change-text on-change-text
:error? (not (string/blank? error-msg))}]
(when-not (string/blank? error-msg)
[quo/info-message
{:type :error
:size :default
:icon :i/info
:container-style style/error-container}
error-msg])]]))

View File

@ -1,11 +1,11 @@
(ns status-im.contexts.settings.wallet.keypairs-and-accounts.view (ns status-im.contexts.settings.wallet.keypairs-and-accounts.view
(:require [quo.core :as quo] (:require [quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme] [quo.theme]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.safe-area :as safe-area] [react-native.safe-area :as safe-area]
[status-im.contexts.settings.wallet.keypairs-and-accounts.actions.view :as actions] [status-im.contexts.settings.wallet.keypairs-and-accounts.actions.view :as actions]
[status-im.contexts.settings.wallet.keypairs-and-accounts.style :as style] [status-im.contexts.settings.wallet.keypairs-and-accounts.style :as style]
[status-im.feature-flags :as ff]
[utils.address :as utils] [utils.address :as utils]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -17,13 +17,12 @@
(defn on-options-press (defn on-options-press
[{:keys [drawer-props keypair]}] [{:keys [drawer-props keypair]}]
(rf/dispatch [:show-bottom-sheet (rf/dispatch [:show-bottom-sheet
{:content (fn [] [actions/view {:content (fn [] [actions/view
{:drawer-props drawer-props {:drawer-props drawer-props
:keypair keypair}]) :keypair keypair}])
:blur-background colors/bottom-sheet-background-blur :theme (:theme drawer-props)
:theme (:theme drawer-props) :shell? true}]))
:shell? true}]))
(defn options-drawer-props (defn options-drawer-props
[{{:keys [name]} :keypair [{{:keys [name]} :keypair
@ -70,7 +69,7 @@
{:blur? true {:blur? true
:status-indicator false :status-indicator false
:stored :on-device :stored :on-device
:action :options :action (if default-keypair? :none :options)
:accounts accounts :accounts accounts
:customization-color customization-color :customization-color customization-color
:container-style style/keypair-container-style :container-style style/keypair-container-style
@ -83,17 +82,16 @@
(defn on-missing-keypair-options-press (defn on-missing-keypair-options-press
[_event keypair-data] [_event keypair-data]
(rf/dispatch [:show-bottom-sheet (rf/dispatch [:show-bottom-sheet
{:theme :dark {:theme :dark
:shell? true :shell? true
:blur-background colors/bottom-sheet-background-blur :content (fn [] [actions/view
:content (fn [] [actions/view {:keypair keypair-data
{:keypair keypair-data :drawer-props (options-drawer-props
:drawer-props (options-drawer-props {:theme :dark
{:theme :dark :type :keypair
:type :keypair :stored :missing
:stored :missing :blur? true
:blur? true :keypair keypair-data})}])}]))
:keypair keypair-data})}])}]))
(defn view (defn view
[] []
@ -127,6 +125,7 @@
:header (when (seq missing-keypairs) :header (when (seq missing-keypairs)
[quo/missing-keypairs [quo/missing-keypairs
{:blur? true {:blur? true
:show-import-all? (ff/enabled? ::ff/settings.import-all-keypairs)
:keypairs missing-keypairs :keypairs missing-keypairs
:on-import-press on-import-press :on-import-press on-import-press
:container-style style/missing-keypairs-container-style :container-style style/missing-keypairs-container-style

View File

@ -1,6 +1,5 @@
(ns status-im.contexts.settings.wallet.network-settings.view (ns status-im.contexts.settings.wallet.network-settings.view
(:require [quo.core :as quo] (:require [quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.foundations.resources :as resources] [quo.foundations.resources :as resources]
[quo.theme] [quo.theme]
[react-native.core :as rn] [react-native.core :as rn]
@ -80,12 +79,11 @@
(defn on-change-testnet (defn on-change-testnet
[{:keys [enable? blur? theme]}] [{:keys [enable? blur? theme]}]
(rf/dispatch [:show-bottom-sheet (rf/dispatch [:show-bottom-sheet
{:content (fn [] [testnet/view {:content (fn [] [testnet/view
{:enable? enable? {:enable? enable?
:blur? blur?}]) :blur? blur?}])
:theme theme :theme theme
:shell? blur? :shell? blur?}]))
:blur-background colors/bottom-sheet-background-blur}]))
(defn view (defn view
[] []

View File

@ -2,7 +2,6 @@
(:require (:require
[clojure.string :as string] [clojure.string :as string]
[quo.core :as quo] [quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.platform :as platform] [react-native.platform :as platform]
[status-im.constants :as constants] [status-im.constants :as constants]
@ -52,11 +51,10 @@
open-remove-confirmation-sheet (rn/use-callback open-remove-confirmation-sheet (rn/use-callback
#(rf/dispatch #(rf/dispatch
[:show-bottom-sheet [:show-bottom-sheet
{:theme :dark {:theme :dark
:shell? true :shell? true
:blur-background colors/bottom-sheet-background-blur :content (fn []
:content (fn [] [remove-address/view opts])}])
[remove-address/view opts])}])
[opts]) [opts])
open-show-address-qr (rn/use-callback open-show-address-qr (rn/use-callback
#(rf/dispatch [:open-modal #(rf/dispatch [:open-modal

View File

@ -16,11 +16,10 @@
(defn basic-settings-options (defn basic-settings-options
[] []
[(when (ff/enabled? ::ff/settings.keypairs-and-accounts) [{:title (i18n/label :t/keypairs-and-accounts)
{:title (i18n/label :t/keypairs-and-accounts) :blur? true
:blur? true :on-press open-keypairs-and-accounts-settings-modal
:on-press open-keypairs-and-accounts-settings-modal :action :arrow}
:action :arrow})
(when (ff/enabled? ::ff/settings.saved-addresses) (when (ff/enabled? ::ff/settings.saved-addresses)
{:title (i18n/label :t/saved-addresses) {:title (i18n/label :t/saved-addresses)
:blur? true :blur? true
@ -74,7 +73,5 @@
[quo/page-top [quo/page-top
{:title (i18n/label :t/wallet) {:title (i18n/label :t/wallet)
:title-accessibility-label :wallet-settings-header}] :title-accessibility-label :wallet-settings-header}]
(when (or (ff/enabled? ::ff/settings.keypairs-and-accounts) [basic-settings]
(ff/enabled? ::ff/settings.saved-addresses))
[basic-settings])
[advanced-settings]])) [advanced-settings]]))

View File

@ -73,7 +73,7 @@
(fn [resolver rejecter] (fn [resolver rejecter]
(json-rpc/call {:method "accounts_makeSeedPhraseKeypairFullyOperable" (json-rpc/call {:method "accounts_makeSeedPhraseKeypairFullyOperable"
:params [(security/safe-unmask-data mnemonic) :params [(security/safe-unmask-data mnemonic)
(-> password security/safe-unmask-data native-module/sha3)] (security/safe-unmask-data password)]
:on-error (fn [error] :on-error (fn [error]
(rejecter (ex-info (str error) {:error error}))) (rejecter (ex-info (str error) {:error error})))
:on-success (fn [value] :on-success (fn [value]

View File

@ -529,18 +529,17 @@
:wallet/process-keypair-from-backup :wallet/process-keypair-from-backup
(fn [{:keys [db]} [{:keys [backedUpKeypair]}]] (fn [{:keys [db]} [{:keys [backedUpKeypair]}]]
(let [{:keys [key-uid accounts]} backedUpKeypair (let [{:keys [key-uid accounts]} backedUpKeypair
updated-keypairs (assoc-in db accounts-fx
[:wallet :keypairs key-uid] (mapv (fn [{:keys [chat] :as account}]
(data-store/rpc->keypair backedUpKeypair)) ;; We exclude the chat account from the profile keypair for fetching the assets
accounts-fx (mapv (fn [{:keys [chat] :as account}] (when-not chat
;; We exclude the chat account from the profile keypair [:dispatch
;; for fetching the assets [:wallet/process-account-from-signal
(when-not chat account]]))
[:dispatch accounts)]
[:wallet/process-account-from-signal {:db (assoc-in db
account]])) [:wallet :keypairs key-uid]
accounts)] (data-store/rpc->keypair backedUpKeypair))
{:db (assoc-in db [:wallet :keypairs] updated-keypairs)
:fx accounts-fx}))) :fx accounts-fx})))
(rf/reg-event-fx (rf/reg-event-fx

View File

@ -11,10 +11,10 @@
(def ^:private initial-flags (def ^:private initial-flags
{::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED) {::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED)
::settings.keypairs-and-accounts (enabled-in-env?
:FLAG_WALLET_SETTINGS_KEYPAIRS_AND_ACCOUNTS_ENABLED)
::settings.saved-addresses (enabled-in-env? ::settings.saved-addresses (enabled-in-env?
:FLAG_WALLET_SETTINGS_SAVED_ADDRESSES_ENABLED) :FLAG_WALLET_SETTINGS_SAVED_ADDRESSES_ENABLED)
::settings.import-all-keypairs (enabled-in-env?
:FLAG_WALLET_SETTINGS_IMPORT_ALL_KEYPAIRS)
::shell.jump-to (enabled-in-env? :ENABLE_JUMP_TO) ::shell.jump-to (enabled-in-env? :ENABLE_JUMP_TO)
::wallet.advanced-sending (enabled-in-env? :FLAG_ADVANCED_SENDING) ::wallet.advanced-sending (enabled-in-env? :FLAG_ADVANCED_SENDING)
::wallet.assets-modal-hide (enabled-in-env? :FLAG_ASSETS_MODAL_HIDE) ::wallet.assets-modal-hide (enabled-in-env? :FLAG_ASSETS_MODAL_HIDE)

View File

@ -542,7 +542,7 @@
:component wallet-options/view} :component wallet-options/view}
{:name :screen/settings.rename-keypair {:name :screen/settings.rename-keypair
:options (assoc options/dark-screen :sheet? true) :options options/transparent-screen-options
:component keypair-rename/view} :component keypair-rename/view}
{:name :screen/settings.encrypted-keypair-qr {:name :screen/settings.encrypted-keypair-qr
@ -558,7 +558,7 @@
:component keypairs-and-accounts/view} :component keypairs-and-accounts/view}
{:name :screen/settings.scan-keypair-qr {:name :screen/settings.scan-keypair-qr
:options options/transparent-modal-screen-options :options options/transparent-screen-options
:component scan-keypair-qr/view} :component scan-keypair-qr/view}
{:name :screen/settings.missing-keypair.import-seed-phrase {:name :screen/settings.missing-keypair.import-seed-phrase

View File

@ -205,6 +205,12 @@
(fn [{:keys [keypairs]}] (fn [{:keys [keypairs]}]
(vals keypairs))) (vals keypairs)))
(rf/reg-sub
:wallet/keypair-names
:<- [:wallet/keypairs-list]
(fn [keypairs]
(set (map :name keypairs))))
(rf/reg-sub (rf/reg-sub
:wallet/selected-keypair-uid :wallet/selected-keypair-uid
:<- [:wallet/create-account] :<- [:wallet/create-account]
@ -244,7 +250,7 @@
size 32}}] size 32}}]
(->> accounts (->> accounts
(keep (fn [{:keys [path color emoji name address]}] (keep (fn [{:keys [path color emoji name address]}]
(when-not (string/starts-with? path constants/path-eip1581) (when-not (string/starts-with? (str path) constants/path-eip1581)
{:account-props {:customization-color color {:account-props {:customization-color color
:size size :size size
:emoji emoji :emoji emoji

View File

@ -643,18 +643,20 @@
:removed false}) :removed false})
(def profile-key-pair-key-uid "abc") (def profile-key-pair-key-uid "abc")
(def profile-key-pair-name "My Profile")
(def seed-phrase-key-pair-key-uid "def") (def seed-phrase-key-pair-key-uid "def")
(def seed-phrase-key-pair-name "My Key Pair")
(def profile-keypair (def profile-keypair
{:key-uid profile-key-pair-key-uid {:key-uid profile-key-pair-key-uid
:name "My Profile" :name profile-key-pair-name
:type :profile :type :profile
:lowest-operability :fully :lowest-operability :fully
:accounts []}) :accounts []})
(def seed-phrase-keypair (def seed-phrase-keypair
{:key-uid seed-phrase-key-pair-key-uid {:key-uid seed-phrase-key-pair-key-uid
:name "My Key Pair" :name seed-phrase-key-pair-name
:type :seed :type :seed
:lowest-operability :no :lowest-operability :no
:accounts []}) :accounts []})
@ -676,6 +678,14 @@
(is (= 2 (count result))) (is (= 2 (count result)))
(is (match? expected result)))) (is (match? expected result))))
(h/deftest-sub :wallet/keypair-names
[sub-name]
(swap! rf-db/app-db assoc-in
[:wallet :keypairs]
{profile-key-pair-key-uid profile-keypair
seed-phrase-key-pair-key-uid seed-phrase-keypair})
(is (match? #{seed-phrase-key-pair-name profile-key-pair-name} (rf/sub [sub-name]))))
(h/deftest-sub :wallet/settings-keypairs-accounts (h/deftest-sub :wallet/settings-keypairs-accounts
[sub-name] [sub-name]
(testing "returns formatted key-pairs and accounts" (testing "returns formatted key-pairs and accounts"

View File

@ -1302,6 +1302,8 @@
"scan-with-status-app": "Scan with the Status app on another device", "scan-with-status-app": "Scan with the Status app on another device",
"scan-key-pairs-qr-code": "Scan key pairs QR code", "scan-key-pairs-qr-code": "Scan key pairs QR code",
"invalid-qr": "Oops! This QR doesnt work with Status", "invalid-qr": "Oops! This QR doesnt work with Status",
"invalid-key-pair-qr": "This does not look like a key pair QR code",
"incorrect-qr-code": "This is not the QR code you are looking for",
"search": "Search", "search": "Search",
"search-discover-communities": "Search communities or categories", "search-discover-communities": "Search communities or categories",
"secret-keys-confirmation-text": "You will need them to continue to use your Keycard in case you ever lose your phone.", "secret-keys-confirmation-text": "You will need them to continue to use your Keycard in case you ever lose your phone.",
@ -2639,6 +2641,7 @@
"max": "Max: {{number}}", "max": "Max: {{number}}",
"your-key-pair-name-is-too-long": "Your key pair name is too long", "your-key-pair-name-is-too-long": "Your key pair name is too long",
"your-key-pair-name-is-too-short": "Your key pair name is too short", "your-key-pair-name-is-too-short": "Your key pair name is too short",
"key-name-error-taken": "Key pair name already in use",
"key-name-error-length": "Key name too long", "key-name-error-length": "Key name too long",
"key-name-error-emoji": "Emojis are not allowed", "key-name-error-emoji": "Emojis are not allowed",
"key-name-error-special-char": "Special characters are not allowed", "key-name-error-special-char": "Special characters are not allowed",