From 4f0a49f7bff32ed43ae16562da95f402c59e64f2 Mon Sep 17 00:00:00 2001 From: Sean Hagstrom Date: Fri, 10 May 2024 10:53:35 +0100 Subject: [PATCH] Add screen for key-pairs and accounts inside wallet settings (#19912) * chore: add "key pairs and accounts" label * chore: feature flag wallet-settings * tidy: extact navigate-back function into static defn * wip: add initial keypairs and accounts list view to wallet settings * tweak: wire-up initial action menu for key-pairs * tidy: extract key-pair container styles into style namespace * tweak: fix dark background for key-pair and account settings * tidy: refactor on-press handler for key-pair options * fix: move feature-flag usage to settings screen instead of settings items definition * tidy: remove unneeded key props * tidy: clean up de-structuring and passing of props * tidy: use keep with when expressions instead of filter and map expressions * tidy: rename the wallet-settings feature flag * tweak: rename and add feature-flags for mobile wallet settings * tweak: use scrollview for feature-flags and add spacing between feature-flag groups * tweak: adjust the way feature-flags are displayed in groups * tidy: remove unneeded prop * tidy: use bottom-inset for padding key-pair and accounts list * tidy: change `filterv` to `filter` * tidy: use subscription for building account-props * tidy: use subscription to build the entire keypair-account * tweak: use key-pair type to determine default key-pair * tidy: rename component to settings-category-view * tidy: use assoc instead of merge * tidy: extract function from subscription * test: add tests for formatting key-pairs and accounts for wallet settings * tweak: use `match?` instead of `=` * tidy: use `swap!` without anonymous functions --- .../contexts/preview/feature_flags/style.cljs | 5 +- .../contexts/preview/feature_flags/view.cljs | 42 +++---- .../contexts/profile/settings/list_items.cljs | 14 ++- .../contexts/profile/settings/view.cljs | 21 ++-- .../keypairs_and_accounts/actions/view.cljs | 6 + .../wallet/keypairs_and_accounts/style.cljs | 18 +++ .../wallet/keypairs_and_accounts/view.cljs | 89 +++++++++++++++ .../settings/wallet/wallet_options/view.cljs | 21 +++- src/status_im/feature_flags.cljs | 3 + src/status_im/navigation/screens.cljs | 7 ++ src/status_im/subs/wallet/wallet.cljs | 29 +++++ src/status_im/subs/wallet/wallet_test.cljs | 103 ++++++++++++++++++ translations/en.json | 1 + 13 files changed, 317 insertions(+), 42 deletions(-) create mode 100644 src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs create mode 100644 src/status_im/contexts/settings/wallet/keypairs_and_accounts/style.cljs create mode 100644 src/status_im/contexts/settings/wallet/keypairs_and_accounts/view.cljs diff --git a/src/status_im/contexts/preview/feature_flags/style.cljs b/src/status_im/contexts/preview/feature_flags/style.cljs index 63e9f98a7c..ba0d92973d 100644 --- a/src/status_im/contexts/preview/feature_flags/style.cljs +++ b/src/status_im/contexts/preview/feature_flags/style.cljs @@ -1,5 +1,6 @@ (ns status-im.contexts.preview.feature-flags.style) (def container - {:flex 1 - :margin-left 20}) + {:flex 1 + :margin-left 20 + :margin-bottom 20}) diff --git a/src/status_im/contexts/preview/feature_flags/view.cljs b/src/status_im/contexts/preview/feature_flags/view.cljs index a660e0396b..4a4c11e5b7 100644 --- a/src/status_im/contexts/preview/feature_flags/view.cljs +++ b/src/status_im/contexts/preview/feature_flags/view.cljs @@ -18,23 +18,25 @@ :on-press #(rf/dispatch [:navigate-back]) :right-side [{:icon-name :i/rotate :on-press #(ff/reset-flags)}]}] - (doall - (for [context-name ff/feature-flags-categories - :let [context-flags (filter (fn [[k]] - (string/includes? (str k) context-name)) - (ff/feature-flags))]] - ^{:key (str context-name)} - [rn/view {:style style/container} - [quo/text - context-name] - (doall - (for [i (range (count context-flags)) - :let [[flag] (nth context-flags i)]] - ^{:key (str context-name flag i)} - [rn/view {:style {:flex-direction :row}} - [quo/selectors - {:type :toggle - :checked? (ff/enabled? flag) - :container-style {:margin-right 8} - :on-change #(ff/toggle flag)}] - [quo/text (second (string/split (name flag) "."))]]))]))]) + [rn/scroll-view + (doall + (for [context-name ff/feature-flags-categories + :let [context-flags (filter (fn [[k]] + (= context-name + (first (string/split (str (name k)) ".")))) + (ff/feature-flags))]] + ^{:key (str context-name)} + [rn/view {:style style/container} + [quo/text + context-name] + (doall + (for [i (range (count context-flags)) + :let [[flag] (nth context-flags i)]] + ^{:key (str context-name flag i)} + [rn/view {:style {:flex-direction :row}} + [quo/selectors + {:type :toggle + :checked? (ff/enabled? flag) + :container-style {:margin-right 8} + :on-change #(ff/toggle flag)}] + [quo/text (second (string/split (name flag) "."))]]))]))]]) diff --git a/src/status_im/contexts/profile/settings/list_items.cljs b/src/status_im/contexts/profile/settings/list_items.cljs index 26f84d709d..e1887e209b 100644 --- a/src/status_im/contexts/profile/settings/list_items.cljs +++ b/src/status_im/contexts/profile/settings/list_items.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.profile.settings.list-items (:require [status-im.common.not-implemented :as not-implemented] [status-im.config :as config] + [status-im.feature-flags :as ff] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -34,12 +35,13 @@ :image :icon :blur? true :action :arrow} - {:title (i18n/label :t/wallet) - :on-press #(rf/dispatch [:open-modal :screen/settings.wallet]) - :image-props :i/wallet - :image :icon - :blur? true - :action :arrow} + {:title (i18n/label :t/wallet) + :on-press #(rf/dispatch [:open-modal :screen/settings.wallet]) + :image-props :i/wallet + :image :icon + :blur? true + :action :arrow + :feature-flag ::ff/settings.wallet-settings} (when config/show-not-implemented-features? {:title (i18n/label :t/dapps) :on-press not-implemented/alert diff --git a/src/status_im/contexts/profile/settings/view.cljs b/src/status_im/contexts/profile/settings/view.cljs index d371d05ef1..aa4e85a263 100644 --- a/src/status_im/contexts/profile/settings/view.cljs +++ b/src/status_im/contexts/profile/settings/view.cljs @@ -9,18 +9,24 @@ [status-im.contexts.profile.settings.list-items :as settings.items] [status-im.contexts.profile.settings.style :as style] [status-im.contexts.profile.utils :as profile.utils] + [status-im.feature-flags :as ff] [utils.debounce :as debounce] [utils.i18n :as i18n] [utils.re-frame :as rf])) -(defn- settings-item-view +(defn show-settings-item? + [{:keys [feature-flag]}] + (or (ff/enabled? feature-flag) + (nil? feature-flag))) + +(defn- settings-category-view [data] [rf/delay-render [quo/category {:list-type :settings :container-style {:padding-bottom 12} :blur? true - :data data}]]) + :data (filter show-settings-item? data)}]]) (defn scroll-handler [event scroll-y] @@ -52,8 +58,7 @@ on-scroll (rn/use-callback #(scroll-handler % scroll-y))] [quo/overlay {:type :shell} [rn/view - {:key :header - :style (style/navigation-wrapper {:customization-color customization-color + {:style (style/navigation-wrapper {:customization-color customization-color :inset (:top insets) :theme theme})} [quo/page-nav @@ -72,11 +77,10 @@ {:options {:message (:universal-profile-url profile)}}])}]}]] [rn/flat-list - {:key :list - :header [settings.header/view {:scroll-y scroll-y}] + {:header [settings.header/view {:scroll-y scroll-y}] :data (settings.items/items (boolean (:mnemonic profile))) :shows-vertical-scroll-indicator false - :render-fn settings-item-view + :render-fn settings-category-view :get-item-layout get-item-layout :footer [footer insets logout-press] :scroll-event-throttle 16 @@ -84,8 +88,7 @@ :bounces false :over-scroll-mode :never}] [quo/floating-shell-button - {:key :shell - :jump-to + {:jump-to {:on-press #(rf/dispatch [:shell/navigate-to-jump-to]) :customization-color customization-color :label (i18n/label :t/jump-to)}} 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 new file mode 100644 index 0000000000..5b0c999b4b --- /dev/null +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs @@ -0,0 +1,6 @@ +(ns status-im.contexts.settings.wallet.keypairs-and-accounts.actions.view + (:require [quo.core :as quo])) + +(defn view + [props] + [quo/drawer-top props]) diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/style.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/style.cljs new file mode 100644 index 0000000000..4ab69d0c5f --- /dev/null +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/style.cljs @@ -0,0 +1,18 @@ +(ns status-im.contexts.settings.wallet.keypairs-and-accounts.style) + +(def title-container + {:padding-horizontal 20 + :margin-vertical 12}) + +(defn page-wrapper + [top-inset] + {:padding-top top-inset + :flex 1}) + +(defn list-container + [bottom-inset] + {:padding-bottom bottom-inset}) + +(def keypair-container-style + {:margin-horizontal 20 + :margin-vertical 8}) 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 new file mode 100644 index 0000000000..53fba8cf3a --- /dev/null +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/view.cljs @@ -0,0 +1,89 @@ +(ns status-im.contexts.settings.wallet.keypairs-and-accounts.view + (:require [quo.core :as quo] + [quo.theme] + [react-native.core :as rn] + [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.style :as style] + [utils.address :as utils] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn navigate-back + [] + (rf/dispatch [:navigate-back])) + +(defn on-options-press + [{:keys [theme] + :as props}] + (rf/dispatch [:show-bottom-sheet + {:content (fn [] [actions/view props]) + :theme theme}])) + +(defn- keypair + [{keypair-type :type + :keys [accounts name]} + _ _ + {:keys [profile-picture compressed-key customization-color]}] + (let [theme (quo.theme/use-theme) + default-keypair? (= keypair-type :profile) + shortened-key (when default-keypair? + (utils/get-shortened-compressed-key compressed-key)) + on-press (rn/use-callback + (fn [] + (on-options-press + (cond-> {:theme theme + :blur? true + :title name} + default-keypair? + (assoc :type :default-keypair + :description shortened-key + :customization-color customization-color + :profile-picture profile-picture) + (not default-keypair?) + (assoc :type :keypair + :icon-avatar :i/seed)))) + [customization-color default-keypair? name + profile-picture shortened-key theme])] + [quo/keypair + {:blur? false + :status-indicator false + :stored :on-device + :action :options + :accounts accounts + :customization-color customization-color + :container-style style/keypair-container-style + :profile-picture (when default-keypair? profile-picture) + :type (if default-keypair? :default-keypair :other) + :on-options-press on-press + :details {:full-name name + :address shortened-key}}])) + +(defn view + [] + (let [insets (safe-area/get-insets) + compressed-key (rf/sub [:profile/compressed-key]) + profile-picture (rf/sub [:profile/image]) + customization-color (rf/sub [:profile/customization-color]) + quo-keypairs-accounts (rf/sub [:wallet/settings-keypairs-accounts])] + [quo/overlay + {:type :shell + :container-style (style/page-wrapper (:top insets))} + [quo/page-nav + {:key :header + :background :blur + :icon-name :i/arrow-left + :on-press navigate-back}] + [rn/view {:style style/title-container} + [quo/standard-title + {:title (i18n/label :t/keypairs-and-accounts) + :accessibility-label :keypairs-and-accounts-header + :customization-color customization-color}]] + [rn/view {:style {:flex 1}} + [rn/flat-list + {:data quo-keypairs-accounts + :render-fn keypair + :render-data {:profile-picture profile-picture + :compressed-key compressed-key + :customization-color customization-color} + :content-container-style (style/list-container (:bottom insets))}]]])) diff --git a/src/status_im/contexts/settings/wallet/wallet_options/view.cljs b/src/status_im/contexts/settings/wallet/wallet_options/view.cljs index 1ea9387dd1..ddab2704a8 100644 --- a/src/status_im/contexts/settings/wallet/wallet_options/view.cljs +++ b/src/status_im/contexts/settings/wallet/wallet_options/view.cljs @@ -1,8 +1,8 @@ (ns status-im.contexts.settings.wallet.wallet-options.view (:require [quo.core :as quo] - [react-native.core :as rn] [react-native.safe-area :as safe-area] [status-im.contexts.settings.wallet.wallet-options.style :as style] + [status-im.feature-flags :as ff] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -10,9 +10,18 @@ [] (rf/dispatch [:open-modal :screen/settings.saved-addresses])) +(defn open-keypairs-and-accounts-settings-modal + [] + (rf/dispatch [:open-modal :screen/settings.keypairs-and-accounts])) + (defn gen-basic-settings-options [] - [{:title (i18n/label :t/saved-addresses) + [(when (ff/enabled? ::ff/settings.keypairs-and-accounts) + {:title (i18n/label :t/keypairs-and-accounts) + :blur? true + :on-press open-keypairs-and-accounts-settings-modal + :action :arrow}) + {:title (i18n/label :t/saved-addresses) :blur? true :on-press open-saved-addresses-settings-modal :action :arrow}]) @@ -26,11 +35,13 @@ :blur? true :list-type :settings}]) +(defn navigate-back + [] + (rf/dispatch [:navigate-back])) + (defn view [] - (let [inset-top (safe-area/get-top) - navigate-back (rn/use-callback - #(rf/dispatch [:navigate-back]))] + (let [inset-top (safe-area/get-top)] [quo/overlay {:type :shell :container-style (style/page-wrapper inset-top)} diff --git a/src/status_im/feature_flags.cljs b/src/status_im/feature_flags.cljs index 4756771f6e..19d9db6bb4 100644 --- a/src/status_im/feature_flags.cljs +++ b/src/status_im/feature_flags.cljs @@ -11,6 +11,9 @@ (def ^:private initial-flags {::community.edit-account-selection (enabled-in-env? :FLAG_EDIT_ACCOUNT_SELECTION_ENABLED) + ::settings.wallet-settings (enabled-in-env? :FLAG_WALLET_SETTINGS_ENABLED) + ::settings.keypairs-and-accounts (enabled-in-env? + :FLAG_WALLET_SETTINGS_KEYPAIRS_AND_ACCOUNTS_ENABLED) ::wallet.activities (enabled-in-env? :FLAG_WALLET_ACTIVITY_ENABLED) ::wallet.assets-modal-hide (enabled-in-env? :FLAG_ASSETS_MODAL_HIDE) ::wallet.assets-modal-manage-tokens (enabled-in-env? :FLAG_ASSETS_MODAL_MANAGE_TOKENS) diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index c729dec0bb..553ca53c99 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.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] [status-im.contexts.shell.activity-center.view :as activity-center] @@ -501,6 +502,12 @@ :options options/transparent-modal-screen-options :component saved-addresses-settings/view} + {:name :screen/settings.keypairs-and-accounts + :options (merge + options/transparent-modal-screen-options + options/dark-screen) + :component keypairs-and-accounts/view} + {:name :screen/settings-messages :options options/transparent-modal-screen-options :component settings.messages/view} diff --git a/src/status_im/subs/wallet/wallet.cljs b/src/status_im/subs/wallet/wallet.cljs index 1bd4df5912..d89cf853ae 100644 --- a/src/status_im/subs/wallet/wallet.cljs +++ b/src/status_im/subs/wallet/wallet.cljs @@ -1,6 +1,7 @@ (ns status-im.subs.wallet.wallet (:require [clojure.string :as string] [re-frame.core :as rf] + [status-im.constants :as constants] [status-im.contexts.wallet.common.utils :as utils] [status-im.contexts.wallet.common.utils.networks :as network-utils] [status-im.subs.wallet.add-account.address-to-watch] @@ -158,6 +159,34 @@ :goerli-enabled? goerli-enabled?}) selected-networks)))) +(defn- format-settings-keypair-accounts + [accounts + {:keys [networks size] + :or {networks [] + size 32}}] + (->> accounts + (keep (fn [{:keys [path customization-color emoji name address]}] + (when-not (string/starts-with? path constants/path-eip1581) + {:account-props {:customization-color customization-color + :size size + :emoji emoji + :type :default + :name name + :address address} + :networks networks + :state :default + :action :none}))))) + +(rf/reg-sub + :wallet/settings-keypairs-accounts + :<- [:wallet/keypairs] + (fn [keypairs [_ format-options]] + (->> keypairs + (map (fn [{:keys [accounts name type]}] + {:type (keyword type) + :name name + :accounts (format-settings-keypair-accounts accounts format-options)}))))) + (rf/reg-sub :wallet/derivation-path-state :<- [:wallet/create-account] diff --git a/src/status_im/subs/wallet/wallet_test.cljs b/src/status_im/subs/wallet/wallet_test.cljs index 53696705ff..3c1ee66ae2 100644 --- a/src/status_im/subs/wallet/wallet_test.cljs +++ b/src/status_im/subs/wallet/wallet_test.cljs @@ -554,6 +554,109 @@ (= keypairs (rf/sub [sub-name]))))) +(def chat-account + {:path "m/43'/60'/1581'/0'/0" + :emoji "" + :key-uid "abc" + :address "address-1" + :color-id "" + :wallet false + :name "My Profile" + :type "generated" + :chat true + :customization-color :blue + :hidden false + :removed false}) + +(def wallet-account + {:path "m/44'/60'/0'/0/0" + :emoji "🤡" + :key-uid "abc" + :address "address-2" + :wallet true + :name "My Account" + :type "generated" + :chat false + :customization-color :primary + :hidden false + :removed false}) + +(def keypairs-accounts + {:key-uid "abc" + :name "My Profile" + :type "profile" + :accounts []}) + +(h/deftest-sub :wallet/settings-keypairs-accounts + [sub-name] + (testing "returns formatted key-pairs and accounts" + (swap! rf-db/app-db + assoc-in + [:wallet :keypairs] + [(assoc keypairs-accounts + :accounts + [wallet-account])]) + + (let [{:keys [customization-color name address emoji]} wallet-account] + (is + (match? [{:name (:name keypairs-accounts) + :type (keyword (:type keypairs-accounts)) + :accounts [{:account-props {:customization-color customization-color + :size 32 + :emoji emoji + :type :default + :name name + :address address} + :networks [] + :state :default + :action :none}]}] + (rf/sub [sub-name]))))) + + (testing "allows for passing account format options" + (swap! rf-db/app-db + assoc-in + [:wallet :keypairs] + [(assoc keypairs-accounts + :accounts + [wallet-account])]) + + (let [{:keys [customization-color + name + address + emoji]} wallet-account + network-options [{:network-name :ethereum :short-name "eth"} + {:network-name :optimism :short-name "opt"} + {:network-name :arbitrum :short-name "arb1"}] + size-option 20] + (is + (match? [{:name (:name keypairs-accounts) + :type (keyword (:type keypairs-accounts)) + :accounts [{:account-props {:customization-color customization-color + :size size-option + :emoji emoji + :type :default + :name name + :address address} + :networks network-options + :state :default + :action :none}]}] + (rf/sub [sub-name + {:networks network-options + :size size-option}]))))) + + (testing "filters non-wallet accounts" + (swap! rf-db/app-db + assoc-in + [:wallet :keypairs] + [(assoc keypairs-accounts + :accounts + [chat-account])]) + (is + (match? [{:name (:name keypairs-accounts) + :type (keyword (:type keypairs-accounts)) + :accounts []}] + (rf/sub [sub-name]))))) + (def local-suggestions ["a" "b"]) (h/deftest-sub :wallet/local-suggestions diff --git a/translations/en.json b/translations/en.json index d8ad869bc2..7d21a57d84 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2484,6 +2484,7 @@ "address-no-activity": "This address has no activity", "scanning": "Scanning for activity...", "keypairs": "Key pairs", + "keypairs-and-accounts": "Key pairs and accounts", "keypairs-accounts-and-addresses": "Key pairs, accounts and addresses", "keypairs-description": "Select key pair to derive your new account from", "confirm-account-origin": "Confirm account origin",