diff --git a/resources/images/icons2/12x12/seed-phrase@2x.png b/resources/images/icons2/12x12/seed-phrase@2x.png new file mode 100644 index 0000000000..250bc7ae6e Binary files /dev/null and b/resources/images/icons2/12x12/seed-phrase@2x.png differ diff --git a/resources/images/icons2/12x12/seed-phrase@3x.png b/resources/images/icons2/12x12/seed-phrase@3x.png new file mode 100644 index 0000000000..4fe09f1299 Binary files /dev/null and b/resources/images/icons2/12x12/seed-phrase@3x.png differ diff --git a/src/status_im/common/validation/keypair.cljs b/src/status_im/common/validation/keypair.cljs new file mode 100644 index 0000000000..5a71749817 --- /dev/null +++ b/src/status_im/common/validation/keypair.cljs @@ -0,0 +1,23 @@ +(ns status-im.common.validation.keypair + (:require [clojure.string :as string] + [status-im.common.validation.general :as validators] + [status-im.constants :as constants] + utils.emojilib + [utils.i18n :as i18n])) + +(defn keypair-too-short? + [s] + (< (-> s str string/trim count) constants/key-pair-name-min-length)) + +(defn keypair-too-long? + [s] + (> (-> s str string/trim count) constants/key-pair-name-max-length)) + +(defn validation-keypair-name + [s] + (cond + (string/blank? s) nil + (validators/has-emojis? s) (i18n/label :t/key-name-error-emoji) + (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-long? s) (i18n/label :t/your-key-pair-name-is-too-long))) diff --git a/src/status_im/common/validation/keypair_test.cljs b/src/status_im/common/validation/keypair_test.cljs new file mode 100644 index 0000000000..08d83c0a65 --- /dev/null +++ b/src/status_im/common/validation/keypair_test.cljs @@ -0,0 +1,27 @@ +(ns status-im.common.validation.keypair-test + (:require + [cljs.test :refer-macros [deftest are]] + [status-im.common.validation.keypair :as keypair-validator] + [utils.i18n :as i18n])) + +(deftest keypair-name-too-short-test + (are [arg expected] + (expected (keypair-validator/keypair-too-short? arg)) + "abc" true? + "abcdef" false?)) + +(deftest keypair-name-too-long-test + (are [arg expected] + (expected (keypair-validator/keypair-too-long? arg)) + (apply str (repeat 25 "a")) true? + "abcdef" false?)) + +(deftest validation-keypair-name-test + (are [arg expected] + (= (keypair-validator/validation-keypair-name arg) expected) + nil nil + "" nil + "name !" (i18n/label :t/key-name-error-special-char) + "Hello 😊" (i18n/label :t/key-name-error-emoji) + "abc" (i18n/label :t/your-key-pair-name-is-too-short) + (apply str (repeat 25 "a")) (i18n/label :t/your-key-pair-name-is-too-long))) diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 0422622bb5..b1c9d5086d 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -77,6 +77,9 @@ (def ^:const contact-request-message-state-declined 3) (def ^:const contact-request-message-max-length 280) +(def ^:const key-pair-name-max-length 20) +(def ^:const key-pair-name-min-length 5) + (def request-to-join-pending-state 1) (def reactions diff --git a/src/status_im/contexts/settings/wallet/events.cljs b/src/status_im/contexts/settings/wallet/events.cljs new file mode 100644 index 0000000000..234f97a727 --- /dev/null +++ b/src/status_im/contexts/settings/wallet/events.cljs @@ -0,0 +1,32 @@ +(ns status-im.contexts.settings.wallet.events + (:require + [taoensso.timbre :as log] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(rf/reg-event-fx + :wallet/rename-keypair-success + (fn [{:keys [db]} [key-uid name]] + {:db (update-in db + [:wallet :keypairs] + (fn [keypairs] + (map (fn [keypair] + (if (= (keypair :key-uid) key-uid) + (assoc keypair :name name) + keypair)) + keypairs))) + :fx [[:dispatch [:navigate-back]] + [:dispatch + [:toasts/upsert + {:type :positive + :text (i18n/label :t/key-pair-name-updated)}]]]})) + +(defn rename-keypair + [_ [{:keys [key-uid keypair-name]}]] + {:fx [[:json-rpc/call + [{:method "accounts_updateKeypairName" + :params [key-uid keypair-name] + :on-success [:wallet/rename-keypair-success key-uid keypair-name] + :on-error #(log/info "failed to rename keypair " %)}]]]}) + +(rf/reg-event-fx :wallet/rename-keypair rename-keypair) diff --git a/src/status_im/contexts/settings/wallet/events_test.cljs b/src/status_im/contexts/settings/wallet/events_test.cljs new file mode 100644 index 0000000000..c37b7452bc --- /dev/null +++ b/src/status_im/contexts/settings/wallet/events_test.cljs @@ -0,0 +1,20 @@ +(ns status-im.contexts.settings.wallet.events-test + (:require + [cljs.test :refer-macros [deftest is]] + matcher-combinators.test + [status-im.contexts.settings.wallet.events :as sut])) + +(def key-uid "0xfef454bb492ee4677594f8e05921c84f336fa811deb99b8d922477cc87a38b98") + +(deftest rename-keypair-test + (let [new-keypair-name "key pair new" + cofx {:db {}} + expected {:fx [[:json-rpc/call + [{:method "accounts_updateKeypairName" + :params [key-uid new-keypair-name] + :on-success [:wallet/rename-keypair-success key-uid new-keypair-name] + :on-error fn?}]]]}] + (is (match? expected + (sut/rename-keypair cofx + [{:key-uid key-uid + :keypair-name new-keypair-name}]))))) diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs index 5b0c999b4b..39421e225c 100644 --- a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs @@ -1,6 +1,19 @@ (ns status-im.contexts.settings.wallet.keypairs-and-accounts.actions.view - (:require [quo.core :as quo])) + (:require [quo.core :as quo] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn on-rename-request + [data] + (rf/dispatch [:open-modal :screen/settings.rename-keypair data])) (defn view - [props] - [quo/drawer-top props]) + [props data] + [:<> + [quo/drawer-top props] + [quo/action-drawer + [(when (= (:type props) :keypair) + [{:icon :i/edit + :accessibility-label :rename-key-pair + :label (i18n/label :t/rename-key-pair) + :on-press #(on-rename-request data)}])]]]) diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/rename/style.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/rename/style.cljs new file mode 100644 index 0000000000..ae4d0edb8a --- /dev/null +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/rename/style.cljs @@ -0,0 +1,11 @@ +(ns status-im.contexts.settings.wallet.keypairs-and-accounts.rename.style) + +(def header-container + {:margin-bottom 8}) + +(def bottom-action + {:margin-horizontal -20}) + +(def error-container + {:margin-left 20 + :margin-vertical 8}) diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/rename/view.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/rename/view.cljs new file mode 100644 index 0000000000..8836d1d06f --- /dev/null +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/rename/view.cljs @@ -0,0 +1,84 @@ +(ns status-im.contexts.settings.wallet.keypairs-and-accounts.rename.view + (:require [clojure.string :as string] + [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.floating-button-page.view :as floating-button-page] + [status-im.common.validation.keypair :as keypair-validator] + [status-im.constants :as constants] + [status-im.contexts.settings.wallet.keypairs-and-accounts.rename.style :as style] + [utils.debounce :as debounce] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn navigate-back [] (rf/dispatch [:navigate-back])) + +(defn view + [] + (let [{:keys [name key-uid]} (rf/sub [:get-screen-params]) + customization-color (rf/sub [:profile/customization-color]) + [unsaved-keypair-name set-unsaved-keypair-name] (rn/use-state name) + [error-msg set-error-msg] (rn/use-state nil) + [typing? set-typing?] (rn/use-state false) + validate-keypair-name (rn/use-callback + (debounce/debounce + (fn [name] + (set-error-msg + (keypair-validator/validation-keypair-name + name)) + (set-typing? false)) + 300)) + on-change-text (rn/use-callback (fn [text] + (set-typing? true) + (set-unsaved-keypair-name + text) + (validate-keypair-name text))) + on-clear (rn/use-callback (fn [] + (on-change-text ""))) + on-save (rn/use-callback #(rf/dispatch + [:wallet/rename-keypair + {:key-uid key-uid + :keypair-name + unsaved-keypair-name}]) + [unsaved-keypair-name key-uid])] + [floating-button-page/view + {:header [quo/page-nav + {:icon-name :i/close + :on-press navigate-back + :accessibility-label :top-bar}] + :footer [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/save) + :button-one-props {:disabled? (or typing? + (string/blank? unsaved-keypair-name) + (not (string/blank? error-msg))) + :customization-color customization-color + :on-press on-save} + :container-style style/bottom-action}]} + [quo/page-top + {:container-style style/header-container + :title (i18n/label :t/rename-key-pair) + :description :context-tag + :context-tag {:type :icon + :size 24 + :context name + :icon :i/seed-phrase}}] + [quo/input + {:container-style {:margin-horizontal 20} + :placeholder (i18n/label :t/keypair-name-input-placeholder) + :label (i18n/label :t/keypair-name) + :default-value unsaved-keypair-name + :char-limit constants/key-pair-name-max-length + :max-length 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])])) + diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/view.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/view.cljs index 53fba8cf3a..070c53f679 100644 --- a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/view.cljs +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/view.cljs @@ -15,14 +15,15 @@ (defn on-options-press [{:keys [theme] - :as props}] + :as props} keypair] (rf/dispatch [:show-bottom-sheet - {:content (fn [] [actions/view props]) + {:content (fn [] [actions/view props keypair]) :theme theme}])) (defn- keypair [{keypair-type :type - :keys [accounts name]} + :keys [accounts name] + :as item} _ _ {:keys [profile-picture compressed-key customization-color]}] (let [theme (quo.theme/use-theme) @@ -42,7 +43,8 @@ :profile-picture profile-picture) (not default-keypair?) (assoc :type :keypair - :icon-avatar :i/seed)))) + :icon-avatar :i/seed)) + item)) [customization-color default-keypair? name profile-picture shortened-key theme])] [quo/keypair diff --git a/src/status_im/contexts/wallet/add_account/create_account/key_pair_name/view.cljs b/src/status_im/contexts/wallet/add_account/create_account/key_pair_name/view.cljs index a2c3cf1137..254b9a2158 100644 --- a/src/status_im/contexts/wallet/add_account/create_account/key_pair_name/view.cljs +++ b/src/status_im/contexts/wallet/add_account/create_account/key_pair_name/view.cljs @@ -6,16 +6,14 @@ [status-im.common.floating-button-page.view :as floating-button-page] [status-im.common.not-implemented :as not-implemented] [status-im.common.validation.general :as validators] + [status-im.constants :as constants] [status-im.contexts.wallet.add-account.create-account.key-pair-name.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) -(def ^:private key-pair-name-max-length 15) -(def ^:private key-pair-name-min-length 5) - (def error-messages {:too-long (i18n/label :t/key-name-error-length) - :too-short (i18n/label :t/key-name-error-too-short {:count key-pair-name-min-length}) + :too-short (i18n/label :t/key-name-error-too-short {:count constants/key-pair-name-min-length}) :emoji (i18n/label :t/key-name-error-emoji) :special-char (i18n/label :t/key-name-error-special-char)}) @@ -33,10 +31,10 @@ (fn [value] (set-key-pair-name value) (cond - (> (count value) key-pair-name-max-length) + (> (count value) constants/key-pair-name-max-length) (set-error :too-long) - (< 0 (count value) key-pair-name-min-length) + (< 0 (count value) constants/key-pair-name-min-length) (set-error :too-short) (validators/has-emojis? value) @@ -81,7 +79,7 @@ {:container-style style/input :placeholder (i18n/label :t/keypair-name-input-placeholder) :label (i18n/label :t/keypair-name) - :char-limit key-pair-name-max-length + :char-limit constants/key-pair-name-max-length :auto-focus true :on-change-text on-change-text :error error}] diff --git a/src/status_im/contexts/wallet/add_account/create_account/new_keypair/keypair_name/view.cljs b/src/status_im/contexts/wallet/add_account/create_account/new_keypair/keypair_name/view.cljs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/status_im/contexts/wallet/events.cljs b/src/status_im/contexts/wallet/events.cljs index 3ee4328c9c..107b31a427 100644 --- a/src/status_im/contexts/wallet/events.cljs +++ b/src/status_im/contexts/wallet/events.cljs @@ -4,6 +4,7 @@ [clojure.string :as string] [react-native.platform :as platform] [status-im.constants :as constants] + [status-im.contexts.settings.wallet.events] [status-im.contexts.wallet.common.utils.networks :as network-utils] [status-im.contexts.wallet.data-store :as data-store] [status-im.contexts.wallet.db :as db] diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index c2fd8d9948..1d9a7ac342 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -57,6 +57,7 @@ [status-im.contexts.profile.settings.screens.password.change-password.view :as change-password] [status-im.contexts.profile.settings.screens.password.view :as settings-password] [status-im.contexts.profile.settings.view :as settings] + [status-im.contexts.settings.wallet.keypairs-and-accounts.rename.view :as keypair-rename] [status-im.contexts.settings.wallet.keypairs-and-accounts.view :as keypairs-and-accounts] [status-im.contexts.settings.wallet.saved-addresses.view :as saved-addresses-settings] [status-im.contexts.settings.wallet.wallet-options.view :as wallet-options] @@ -503,6 +504,10 @@ :options options/transparent-modal-screen-options :component wallet-options/view} + {:name :screen/settings.rename-keypair + :options (assoc options/dark-screen :sheet? true) + :component keypair-rename/view} + {:name :screen/settings.saved-addresses :options options/transparent-modal-screen-options :component saved-addresses-settings/view} diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index fb50234188..6fd98d73fa 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -183,9 +183,10 @@ :<- [:wallet/keypairs] (fn [keypairs [_ format-options]] (->> keypairs - (map (fn [{:keys [accounts name type]}] + (map (fn [{:keys [accounts name type key-uid]}] {:type (keyword type) :name name + :key-uid key-uid :accounts (format-settings-keypair-accounts accounts format-options)}))))) (rf/reg-sub diff --git a/translations/en.json b/translations/en.json index 6494da800d..ea9f57a78a 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1255,6 +1255,7 @@ "remove-network": "Remove network", "remove-token": "Remove token", "removed": "removed", + "rename-key-pair": "Rename key pair", "repeat-pin": "Repeat new 6-digit passcode", "repeat-puk": "Repeat new 12-digit PUK", "report-bug-email-template": "1. Issue Description\n{{description}}\n\n\n2. Steps to reproduce\n{{steps}}\n\n\n3. Attach screenshots that can demo the problem, please\n", @@ -2570,6 +2571,7 @@ "keypair-name": "Key pair name", "keypair-name-description": "Name key pair for your own personal reference", "keypair-name-input-placeholder": "Collectibles account, Old vault....", + "key-pair-name-updated": "Key pair name updated", "goerli-testnet-toggle-confirmation": "Are you sure you want to toggle Goerli? This will log you out and you will have to login again.", "bridged-to": "Bridged to {{network}}", "slide-to-bridge": "Slide to bridge", @@ -2590,6 +2592,8 @@ "other": "{{count}} addresses" }, "max": "Max: {{number}}", + "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", "key-name-error-length": "Key name too long", "key-name-error-emoji": "Emojis are not allowed", "key-name-error-special-char": "Special characters are not allowed",