Wallet: selecting keypair (#19070)

Wallet: selecting keypair (#19070)
This commit is contained in:
Omar Basem 2024-03-19 22:36:08 +04:00 committed by GitHub
parent 9e5b8b5f9c
commit fc801213ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 267 additions and 187 deletions

View File

@ -4,19 +4,18 @@
[react-native.platform :as platform]))
(defn container
[{:keys [blur? customization-color theme selected?]}]
{:border-radius 16
:border-width 1
:border-color (if selected?
(if blur?
colors/white
(colors/theme-colors (colors/custom-color customization-color 50)
(colors/custom-color customization-color 60)
theme))
(if blur?
colors/white-opa-5
(colors/theme-colors colors/neutral-10 colors/neutral-80 theme)))
:padding-bottom 8})
[{:keys [blur? customization-color theme selected? container-style]}]
(merge {:border-radius 16
:border-width 1
:border-color (if selected?
(if blur?
colors/white
(colors/resolve-color customization-color theme))
(if blur?
colors/white-opa-5
(colors/theme-colors colors/neutral-10 colors/neutral-80 theme)))
:padding-bottom 8}
container-style))
(def header-container
{:padding-horizontal 12

View File

@ -11,7 +11,6 @@
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn keypair-string
@ -34,7 +33,7 @@
:profile-picture profile-picture}]
[icon-avatar/icon-avatar
{:size :size-32
:icon :i/placeholder
:icon :i/seed
:border? true}]))
(defn title-view
@ -91,23 +90,23 @@
[account-list-card/view item])
(defn- view-internal
[{:keys [default-selected?]}]
(let [selected? (reagent/atom default-selected?)]
(fn [{:keys [accounts action container-style] :as props}]
[rn/pressable
{:style (merge (style/container (merge props {:selected? @selected?})) container-style)
:on-press #(when (= action :selector) (reset! selected? (not @selected?)))}
[rn/view {:style style/header-container}
[avatar props]
[rn/view
{:style {:margin-left 8
:flex 1}}
[title-view (assoc props :selected? @selected?)]
[details-view props]]]
[rn/flat-list
{:data accounts
:render-fn acc-list-card
:separator [rn/view {:style {:height 8}}]
:style {:padding-horizontal 8}}]])))
[{:keys [accounts action container-style selected? on-press] :as props}]
[rn/pressable
{:style (style/container (merge props
{:selected? selected?
:container-style container-style}))
:on-press #(when (= action :selector) (on-press))}
[rn/view {:style style/header-container}
[avatar props]
[rn/view
{:style {:margin-left 8
:flex 1}}
[title-view (assoc props :selected? selected?)]
[details-view props]]]
[rn/flat-list
{:data accounts
:render-fn acc-list-card
:separator [rn/view {:style {:height 8}}]
:style {:padding-horizontal 8}}]])
(def view (quo.theme/with-theme view-internal))

View File

@ -0,0 +1,51 @@
(ns status-im.contexts.wallet.create-account.events
(:require [status-im.contexts.wallet.data-store :as data-store]
[utils.re-frame :as rf]))
(defn get-keypairs-success
[{:keys [db]} [keypairs]]
(let [parsed-keypairs (data-store/parse-keypairs keypairs)
default-key-uid (:key-uid (first parsed-keypairs))]
{:db (-> db
(assoc-in [:wallet :keypairs] parsed-keypairs)
(assoc-in [:wallet :ui :create-account :selected-keypair-uid] default-key-uid))}))
(rf/reg-event-fx :wallet/get-keypairs-success get-keypairs-success)
(defn confirm-account-origin
[{:keys [db]} [key-uid]]
{:db (assoc-in db [:wallet :ui :create-account :selected-keypair-uid] key-uid)
:fx [[:dispatch [:navigate-back]]]})
(rf/reg-event-fx :wallet/confirm-account-origin confirm-account-origin)
(defn store-secret-phrase
[{:keys [db]} [{:keys [secret-phrase random-phrase]}]]
{:db (-> db
(assoc-in [:wallet :ui :create-account :secret-phrase] secret-phrase)
(assoc-in [:wallet :ui :create-account :random-phrase] random-phrase))
:fx [[:dispatch-later [{:ms 20 :dispatch [:navigate-to :screen/wallet.check-your-backup]}]]]})
(rf/reg-event-fx :wallet/store-secret-phrase store-secret-phrase)
(defn new-keypair-created
[{:keys [db]} [{:keys [new-keypair]}]]
{:db (assoc-in db [:wallet :ui :create-account :new-keypair] new-keypair)
:fx [[:dispatch [:navigate-back-to :wallet-create-account]]]})
(rf/reg-event-fx :wallet/new-keypair-created new-keypair-created)
(defn new-keypair-continue
[{:keys [db]} [{:keys [keypair-name]}]]
(let [secret-phrase (get-in db [:wallet :ui :create-account :secret-phrase])]
{:fx [[:effects.wallet/create-account-from-mnemonic
{:secret-phrase secret-phrase
:keypair-name keypair-name}]]}))
(rf/reg-event-fx :wallet/new-keypair-continue new-keypair-continue)
(defn clear-new-keypair
[{:keys [db]}]
{:db (update-in db [:wallet :ui :create-account] dissoc :new-keypair)})
(rf/reg-event-fx :wallet/clear-new-keypair clear-new-keypair)

View File

@ -0,0 +1,44 @@
(ns status-im.contexts.wallet.create-account.events-test
(:require
[cljs.test :refer-macros [deftest is]]
matcher-combinators.test
[status-im.contexts.wallet.create-account.events :as events]))
(deftest confirm-account-origin
(let [db {:wallet {:ui {:create-account {}}}}
props ["key-uid"]
expected-db {:wallet {:ui {:create-account {:selected-keypair-uid "key-uid"}}}}
effects (events/confirm-account-origin {:db db} props)
result-db (:db effects)]
(is (match? result-db expected-db))))
(deftest store-secret-phrase
(let [db {}
props [{:secret-phrase "test-secret" :random-phrase "random-test"}]
expected-db {:wallet {:ui {:create-account {:secret-phrase "test-secret"
:random-phrase "random-test"}}}}
effects (events/store-secret-phrase {:db db} props)
result-db (:db effects)]
(is (match? result-db expected-db))))
(deftest new-keypair-created
(let [db {}
props [{:new-keypair "test-keypair"}]
expected-db {:wallet {:ui {:create-account {:new-keypair "test-keypair"}}}}
effects (events/new-keypair-created {:db db} props)
result-db (:db effects)]
(is (match? result-db expected-db))))
(deftest new-keypair-continue
(let [db {:wallet {:ui {:create-account {:secret-phrase "test-secret"}}}}
props [{:keypair-name "test-keypair"}]
expected-effects [[:effects.wallet/create-account-from-mnemonic
{:secret-phrase "test-secret" :keypair-name "test-keypair"}]]
effects (events/new-keypair-continue {:db db} props)]
(is (match? effects {:fx expected-effects}))))
(deftest clear-new-keypair
(let [db {:wallet {:ui {:create-account {:new-keypair "test-keypair"}}}}
expected-db {:wallet {:ui {:create-account {}}}}
effects (events/clear-new-keypair {:db db})]
(is (match? (:db effects) expected-db))))

View File

@ -4,7 +4,6 @@
[quo.core :as quo]
[react-native.core :as rn]
[status-im.constants :as constants]
[status-im.contexts.profile.utils :as profile.utils]
[status-im.contexts.wallet.create-account.select-keypair.style :as style]
[utils.address :as utils]
[utils.i18n :as i18n]
@ -47,7 +46,7 @@
:action :none}))))
(defn- keypair
[item index _ {:keys [profile-picture compressed-key]}]
[item index _ {:keys [profile-picture compressed-key selected-key-uid set-selected-key-uid]}]
(let [main-account (first (:accounts item))
color (:customization-color main-account)
accounts (parse-accounts (:accounts item))]
@ -63,16 +62,19 @@
:details {:full-name (:name item)
:address (when (zero? index)
(utils/get-shortened-compressed-key compressed-key))}
:on-press #(set-selected-key-uid (:key-uid item))
:accounts accounts
:default-selected? (zero? index)
:selected? (= selected-key-uid (:key-uid item))
:container-style {:margin-horizontal 20
:margin-vertical 8}}]))
(defn view
[]
(let [{:keys [compressed-key customization-color]} (rf/sub [:profile/profile])
profile-with-image (rf/sub [:profile/profile-with-image])
keypairs (rf/sub [:wallet/keypairs])
profile-picture (profile.utils/photo profile-with-image)]
(let [compressed-key (rf/sub [:profile/compressed-key])
customization-color (rf/sub [:profile/customization-color])
keypairs (rf/sub [:wallet/keypairs])
selected-keypair (rf/sub [:wallet/selected-keypair-uid])
profile-picture (rf/sub [:profile/image])
[selected-key-uid set-selected-key-uid] (rn/use-state selected-keypair)]
[rn/view {:style {:flex 1}}
[quo/page-nav
{:icon-name :i/close
@ -91,12 +93,17 @@
[rn/flat-list
{:data keypairs
:render-fn keypair
:render-data {:profile-picture profile-picture
:compressed-key compressed-key}
:render-data {:profile-picture profile-picture
:compressed-key compressed-key
:selected-key-uid selected-key-uid
:set-selected-key-uid set-selected-key-uid}
:initial-num-to-render 1
:content-container-style {:padding-bottom 60}}]
[quo/bottom-actions
{:actions :one-action
:button-one-label (i18n/label :t/confirm-account-origin)
:button-one-props {:disabled? true
:customization-color customization-color}
:button-one-props {:disabled? (= selected-keypair selected-key-uid)
:customization-color customization-color
:on-press #(rf/dispatch [:wallet/confirm-account-origin
selected-key-uid])}
:container-style style/bottom-action-container}]]))

View File

@ -23,17 +23,19 @@
(defn- get-keypair-data
[primary-name derivation-path account-color {:keys [keypair-name]}]
[{:title (or keypair-name (i18n/label :t/keypair-title {:name primary-name}))
:image (if keypair-name :icon :avatar)
:image-props (if keypair-name
:i/seed
{:full-name (utils.string/get-initials primary-name 1)
[{:keys [title primary-keypair? new-keypair? derivation-path customization-color]}]
[{:title title
:image (if primary-keypair? :avatar :icon)
:image-props (if primary-keypair?
{:full-name (utils.string/get-initials title 1)
:size :xxs
:customization-color account-color})
:action (when-not keypair-name :button)
:customization-color customization-color}
:i/seed)
:action (when-not new-keypair? :button)
:action-props {:on-press (fn []
(rf/dispatch [:navigate-to :screen/wallet.select-keypair]))
(ff/alert ::ff/wallet.bridge-token
#(rf/dispatch [:navigate-to
:screen/wallet.select-keypair])))
:button-text (i18n/label :t/edit)
:alignment :flex-start}
:description :text
@ -42,13 +44,7 @@
:image :icon
:image-props :i/derivated-path
:action :button
:action-props {:on-press (fn []
(ff/alert ::ff/wallet.network-filter
#(rf/dispatch [:navigate-to
:screen/wallet.edit-derivation-path
{:customization-color account-color}])))
:action-props {:on-press #(js/alert "Coming soon!")
:button-text (i18n/label :t/edit)
:icon-left :i/placeholder
:alignment :flex-start}
@ -56,24 +52,51 @@
:description-props {:text (string/replace derivation-path #"/" " / ")}}])
(defn- f-view
[]
(let [top (safe-area/get-top)
bottom (safe-area/get-bottom)
account-color (reagent/atom (rand-nth colors/account-colors))
emoji (reagent/atom (emoji-picker.utils/random-emoji))
number-of-accounts (count (rf/sub [:wallet/accounts-without-watched-accounts]))
account-name (reagent/atom "")
placeholder (i18n/label :t/default-account-placeholder
{:number (inc number-of-accounts)})
derivation-path (reagent/atom (utils/get-derivation-path number-of-accounts))
{:keys [public-key address]} (rf/sub [:profile/profile])
on-change-text #(reset! account-name %)
primary-name (first (rf/sub [:contacts/contact-two-names-by-identity
public-key]))
{window-width :width} (rn/get-window)]
(fn [{:keys [theme]}]
(let [{:keys [new-keypair]} (rf/sub [:wallet/create-account])]
(rn/use-unmount #(rf/dispatch [:wallet/clear-new-keypair]))
[{:keys [theme]}]
(let [account-name (reagent/atom "")
account-color (reagent/atom (rand-nth colors/account-colors))
emoji (reagent/atom (emoji-picker.utils/random-emoji))]
(fn []
(let [top (safe-area/get-top)
bottom (safe-area/get-bottom)
{window-width :width} (rn/get-window)
number-of-accounts (count (rf/sub
[:wallet/accounts-without-watched-accounts]))
{:keys [address customization-color]} (rf/sub [:profile/profile])
{:keys [new-keypair]} (rf/sub [:wallet/create-account])
keypairs (rf/sub [:wallet/keypairs])
selected-keypair-uid (rf/sub [:wallet/selected-keypair-uid])
placeholder (i18n/label :t/default-account-placeholder
{:number (inc number-of-accounts)})
derivation-path (utils/get-derivation-path
number-of-accounts)
keypair (some #(when (= (:key-uid %) selected-keypair-uid)
%)
keypairs)
primary-keypair? (= selected-keypair-uid (:key-uid (first keypairs)))
create-new-keypair-account #(rf/dispatch
[:wallet/add-keypair-and-create-account
{:sha3-pwd (security/safe-unmask-data %)
:new-keypair
(create-account.utils/prepare-new-keypair
{:new-keypair new-keypair
:address address
:account-name @account-name
:account-color @account-color
:emoji @emoji
:derivation-path
derivation-path})}])
create-existing-keypair-account #(rf/dispatch [:wallet/derive-address-and-add-account
{:sha3-pwd (security/safe-unmask-data %)
:emoji @emoji
:color @account-color
:path derivation-path
:account-name @account-name}])
keypair-title (or (:keypair-name new-keypair)
(if primary-keypair?
(i18n/label :t/keypair-title
{:name (:name keypair)})
(:name keypair)))]
[rn/view {:style {:flex 1}}
[quo/page-nav
{:type :no-title
@ -105,7 +128,7 @@
[quo/title-input
{:customization-color @account-color
:placeholder placeholder
:on-change-text on-change-text
:on-change-text #(reset! account-name %)
:max-length constants/wallet-account-name-max-length
:blur? true
:disabled? false
@ -128,32 +151,19 @@
[quo/category
{:list-type :settings
:label (i18n/label :t/origin)
:data (get-keypair-data primary-name @derivation-path @account-color new-keypair)}]
:data (get-keypair-data {:title keypair-title
:primary-keypair? primary-keypair?
:new-keypair? (boolean new-keypair)
:derivation-path derivation-path
:customization-color customization-color})}]
[standard-auth/slide-button
{:size :size-48
:track-text (i18n/label :t/slide-to-create-account)
:customization-color @account-color
:on-auth-success (fn [entered-password]
:on-auth-success (fn [password]
(if new-keypair
(rf/dispatch
[:wallet/add-keypair-and-create-account
{:sha3-pwd (security/safe-unmask-data
entered-password)
:new-keypair (create-account.utils/prepare-new-keypair
{:new-keypair new-keypair
:address address
:account-name @account-name
:account-color @account-color
:emoji @emoji
:derivation-path
@derivation-path})}])
(rf/dispatch [:wallet/derive-address-and-add-account
{:sha3-pwd (security/safe-unmask-data
entered-password)
:emoji @emoji
:color @account-color
:path @derivation-path
:account-name @account-name}])))
(create-new-keypair-account password)
(create-existing-keypair-account password)))
:auth-button-label (i18n/label :t/confirm)
:disabled? (empty? @account-name)
:container-style (style/slide-button-container bottom)}]]))))

View File

@ -1,5 +1,6 @@
(ns status-im.contexts.wallet.data-store
(:require
[camel-snake-kebab.core :as csk]
[camel-snake-kebab.extras :as cske]
[clojure.set :as set]
[clojure.string :as string]
@ -104,22 +105,33 @@
:nativeCurrencySymbol :native-currency-symbol
:nativeCurrencyName :native-currency-symbol})))
(defn rename-color-id-in-data
[data]
(map (fn [item]
(update item
:accounts
(fn [accounts]
(map (fn [account]
(let [renamed-account (set/rename-keys account
{:colorId :customization-color})]
(if (contains? account :colorId)
renamed-account
(assoc renamed-account :customization-color :blue))))
accounts))))
data))
(defn sort-keypairs
[keypairs]
(sort-by #(if (some (fn [account]
(string/starts-with? (:path account) constants/path-eip1581))
(:accounts %))
0
1)
keypairs))
(defn sort-and-rename-keypairs
[keypairs]
(let [sorted-keypairs (sort-keypairs keypairs)]
(map (fn [item]
(update item
:accounts
(fn [accounts]
(map
(fn [{:keys [colorId] :as account}]
(assoc account
:customization-color
(if (seq colorId)
(keyword colorId)
:blue)))
accounts))))
sorted-keypairs)))
(defn parse-keypairs
[keypairs]
(let [renamed-data (rename-color-id-in-data keypairs)]
(cske/transform-keys transforms/->kebab-case-keyword renamed-data)))
(let [renamed-data (sort-and-rename-keypairs keypairs)]
(cske/transform-keys csk/->kebab-case-keyword renamed-data)))

View File

@ -178,14 +178,15 @@
:wallet assoc
:navigate-to-account address
:new-account? true)
:fx [[:dispatch [:wallet/get-accounts]]]}))
:fx [[:dispatch [:wallet/get-accounts]]
[:dispatch [:wallet/clear-new-keypair]]]}))
(rf/reg-event-fx :wallet/add-account
(fn [{:keys [db]}
[{:keys [sha3-pwd emoji account-name color type] :or {type :generated}}
{:keys [public-key address path]}]]
(let [lowercase-address (if address (string/lower-case address) address)
key-uid (get-in db [:profile/profile :key-uid])
key-uid (get-in db [:wallet :ui :create-account :selected-keypair-uid])
account-config {:key-uid (when (= type :generated) key-uid)
:wallet false
:chat false
@ -233,12 +234,6 @@
(rf/reg-event-fx :wallet/get-keypairs get-keypairs)
(defn get-keypairs-success
[{:keys [db]} [keypairs]]
{:db (assoc-in db [:wallet :keypairs] (data-store/parse-keypairs keypairs))})
(rf/reg-event-fx :wallet/get-keypairs-success get-keypairs-success)
(rf/reg-event-fx :wallet/bridge-select-token
(fn [{:keys [db]} [{:keys [token stack-id]}]]
(let [to-address (get-in db [:wallet :current-viewing-account-address])]
@ -410,37 +405,6 @@
:subject title
:message content})]]}))
(defn store-secret-phrase
[{:keys [db]} [{:keys [secret-phrase random-phrase]}]]
{:db (-> db
(assoc-in [:wallet :ui :create-account :secret-phrase] secret-phrase)
(assoc-in [:wallet :ui :create-account :random-phrase] random-phrase))
:fx [[:dispatch-later [{:ms 20 :dispatch [:navigate-to :screens/wallet.check-your-backup]}]]]})
(rf/reg-event-fx :wallet/store-secret-phrase store-secret-phrase)
(defn new-keypair-created
[{:keys [db]} [{:keys [new-keypair]}]]
{:db (assoc-in db [:wallet :ui :create-account :new-keypair] new-keypair)
:fx [[:dispatch [:navigate-back-to :wallet-create-account]]]})
(rf/reg-event-fx :wallet/new-keypair-created new-keypair-created)
(defn new-keypair-continue
[{:keys [db]} [{:keys [keypair-name]}]]
(let [secret-phrase (get-in db [:wallet :ui :create-account :secret-phrase])]
{:fx [[:effects.wallet/create-account-from-mnemonic
{:secret-phrase secret-phrase
:keypair-name keypair-name}]]}))
(rf/reg-event-fx :wallet/new-keypair-continue new-keypair-continue)
(defn clear-new-keypair
[{:keys [db]}]
{:db (update-in db [:wallet :ui :create-account] dissoc :new-keypair)})
(rf/reg-event-fx :wallet/clear-new-keypair clear-new-keypair)
(rf/reg-event-fx
:wallet/blockchain-status-changed
(fn [{:keys [db]} [{:keys [message]}]]

View File

@ -24,37 +24,6 @@
result-db (:db effects)]
(is (match? result-db expected-db))))))
(deftest store-secret-phrase
(let [db {}
props [{:secret-phrase "test-secret" :random-phrase "random-test"}]
expected-db {:wallet {:ui {:create-account {:secret-phrase "test-secret"
:random-phrase "random-test"}}}}
effects (events/store-secret-phrase {:db db} props)
result-db (:db effects)]
(is (match? result-db expected-db))))
(deftest new-keypair-created
(let [db {}
props [{:new-keypair "test-keypair"}]
expected-db {:wallet {:ui {:create-account {:new-keypair "test-keypair"}}}}
effects (events/new-keypair-created {:db db} props)
result-db (:db effects)]
(is (match? result-db expected-db))))
(deftest new-keypair-continue
(let [db {:wallet {:ui {:create-account {:secret-phrase "test-secret"}}}}
props [{:keypair-name "test-keypair"}]
expected-effects [[:effects.wallet/create-account-from-mnemonic
{:secret-phrase "test-secret" :keypair-name "test-keypair"}]]
effects (events/new-keypair-continue {:db db} props)]
(is (match? effects {:fx expected-effects}))))
(deftest clear-new-keypair
(let [db {:wallet {:ui {:create-account {:new-keypair "test-keypair"}}}}
expected-db {:wallet {:ui {:create-account {}}}}
effects (events/clear-new-keypair {:db db})]
(is (match? (:db effects) expected-db))))
(deftest store-collectibles
(testing "flush-collectibles"
(let [collectible-1 {:collectible-data {:image-url "https://..." :animation-url "https://..."}

View File

@ -28,6 +28,7 @@
status-im.contexts.profile.settings.events
status-im.contexts.shell.share.events
status-im.contexts.syncing.events
status-im.contexts.wallet.create-account.events
status-im.contexts.wallet.effects
status-im.contexts.wallet.events
status-im.contexts.wallet.send.events

View File

@ -10,7 +10,7 @@
(defonce ^:private feature-flags-config
(reagent/atom
{::wallet.edit-default-keypair true
{::wallet.edit-default-keypair (enabled-in-env? :FLAG_EDIT_DEFAULT_KEYPAIR)
::wallet.bridge-token (enabled-in-env? :FLAG_BRIDGE_TOKEN_ENABLED)
::wallet.edit-derivation-path (enabled-in-env? :FLAG_EDIT_DERIVATION_PATH)
::wallet.remove-account (enabled-in-env? :FLAG_REMOVE_ACCOUNT_ENABLED)

View File

@ -126,6 +126,12 @@
(fn [profile]
(:peer-syncing-enabled? profile)))
(re-frame/reg-sub
:profile/compressed-key
:<- [:profile/profile]
(fn [{:keys [compressed-key]}]
compressed-key))
(re-frame/reg-sub
:multiaccount/contact
:<- [:profile/profile]
@ -351,6 +357,12 @@
(fn [[profile ens-names port font-file] [_ avatar-opts]]
(replace-multiaccount-image-uri profile ens-names port font-file avatar-opts)))
(re-frame/reg-sub
:profile/image
:<- [:profile/profile-with-image]
(fn [profile]
(profile.utils/photo profile)))
(re-frame/reg-sub
:profile/login-profile
:<- [:profile/login]

View File

@ -99,6 +99,11 @@
:<- [:wallet]
:-> :keypairs)
(rf/reg-sub
:wallet/selected-keypair-uid
:<- [:wallet/create-account]
:-> :selected-keypair-uid)
(rf/reg-sub
:wallet/accounts
:<- [:wallet]

View File

@ -535,3 +535,10 @@
#(assoc-in % [:wallet :ui :search-address :valid-ens-or-address?] true))
(is
(rf/sub [sub-name]))))
(h/deftest-sub :wallet/selected-keypair-uid
[sub-name]
(testing "returns selected keypair uid"
(swap! rf-db/app-db
#(assoc-in % [:wallet :ui :create-account :selected-keypair-uid] "key-uid"))
(is (= "key-uid" (rf/sub [sub-name])))))