feat: Saved addresses - Display list, Show options and Remove (#20221)
This commit: - Adds feature flag for saved addresses - Displays the list of saved addresses in wallet settings - Shows address options on tap of any saved address - Adds the ability to remove saved address - Refactors saved addresses data structure in app-db Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
This commit is contained in:
parent
e04c7f449f
commit
aa159f53ef
|
@ -79,8 +79,7 @@
|
|||
|
||||
(defn- context-tag-subtitle
|
||||
[{:keys [context-tag-type context icon community-logo community-name account-name emoji
|
||||
customization-color
|
||||
full-name profile-picture]}]
|
||||
customization-color full-name profile-picture blur?]}]
|
||||
(let [tag-type (or context-tag-type :account)]
|
||||
[rn/view
|
||||
{:accessibility-label :context-tag-wrapper
|
||||
|
@ -97,7 +96,8 @@
|
|||
:profile-picture profile-picture
|
||||
:full-name full-name
|
||||
:context context
|
||||
:icon icon}]]))
|
||||
:icon icon
|
||||
:blur? blur?}]]))
|
||||
|
||||
(defn- description-subtitle
|
||||
[{:keys [theme blur? description]}]
|
||||
|
@ -142,7 +142,8 @@
|
|||
:profile-picture profile-picture
|
||||
:full-name full-name
|
||||
:context context
|
||||
:icon icon}]
|
||||
:icon icon
|
||||
:blur? blur?}]
|
||||
|
||||
(and (not= :label type) description)
|
||||
[description-subtitle
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
[quo.components.icon :as icon]
|
||||
[quo.components.list-items.saved-address.style :as style]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.components.wallet.address-text.view :as address-text]
|
||||
[quo.foundations.colors :as colors]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[utils.address :as address]))
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn- left-container
|
||||
[{:keys [blur? name ens address customization-color]}]
|
||||
|
@ -23,15 +23,19 @@
|
|||
:size :paragraph-1
|
||||
:style style/name-text}
|
||||
name]
|
||||
[text/text {:size :paragraph-2}
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :monospace
|
||||
:style (style/account-address blur? theme)}
|
||||
(or ens (address/get-shortened-key address))]]]]))
|
||||
(if ens
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
:weight :monospace
|
||||
:style (style/account-address blur? theme)} ens]
|
||||
[address-text/view
|
||||
{:address address
|
||||
:full-address? true
|
||||
:format :short
|
||||
:blur? blur?}])]]))
|
||||
|
||||
(defn view
|
||||
[{:keys [blur? user-props active-state? customization-color on-press on-options-press]
|
||||
[{:keys [blur? user-props active-state? customization-color on-press on-options-press container-style]
|
||||
:or {customization-color :blue
|
||||
blur? false}}]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
|
@ -52,8 +56,9 @@
|
|||
(set-state new-state)))
|
||||
[active-state?])]
|
||||
[rn/pressable
|
||||
{:style (style/container
|
||||
{:state state :blur? blur? :customization-color customization-color})
|
||||
{:style (merge (style/container
|
||||
{:state state :blur? blur? :customization-color customization-color})
|
||||
container-style)
|
||||
:on-press-in on-press-in
|
||||
:on-press-out on-press-out
|
||||
:on-press on-press
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
[:type {:optional true}
|
||||
[:maybe
|
||||
[:enum :default :multiuser :group :channel :community :token :network :multinetwork :account
|
||||
:collectible :address :icon :audio]]]
|
||||
:collectible :address :icon :audio :wallet-user]]]
|
||||
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
|
||||
[:container-style {:optional true} [:maybe :map]]
|
||||
[:blur? {:optional true} [:maybe :boolean]]
|
||||
|
@ -83,6 +83,10 @@
|
|||
[:map
|
||||
[:duration {:optional true} [:maybe :string]]])
|
||||
|
||||
(def ^:private ?wallet-user
|
||||
[:map
|
||||
[:full-name {:optional true} [:maybe :string]]])
|
||||
|
||||
(def ?schema
|
||||
[:=>
|
||||
[:catn
|
||||
|
@ -101,5 +105,6 @@
|
|||
[:collectible [:merge ?collectible ?size ?context-base]]
|
||||
[:address [:merge ?address ?size ?context-base]]
|
||||
[:icon [:merge ?icon ?size ?context-base]]
|
||||
[:audio [:merge ?audio ?context-base]]]]]
|
||||
[:audio [:merge ?audio ?context-base]]
|
||||
[:wallet-user [:merge ?wallet-user ?size ?context-base]]]]]
|
||||
:any])
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
[quo.components.avatars.account-avatar.view :as account-avatar]
|
||||
[quo.components.avatars.group-avatar.view :as group-avatar]
|
||||
[quo.components.avatars.user-avatar.view :as user-avatar]
|
||||
[quo.components.avatars.wallet-user-avatar.view :as wallet-user-avatar]
|
||||
[quo.components.icon :as icons]
|
||||
[quo.components.list-items.preview-list.view :as preview-list]
|
||||
[quo.components.markdown.text :as text]
|
||||
|
@ -162,6 +163,13 @@
|
|||
:icon
|
||||
[icon-tag props]
|
||||
|
||||
:wallet-user
|
||||
[tag-skeleton {:theme theme :size size :text full-name}
|
||||
[wallet-user-avatar/wallet-user-avatar
|
||||
{:full-name full-name
|
||||
:size (if (= size 24) :size-20 :size-24)
|
||||
:customization-color customization-color}]]
|
||||
|
||||
nil)]]))
|
||||
|
||||
(def view (schema/instrument #'view-internal component-schema/?schema))
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
(ns status-im.contexts.settings.wallet.saved-addresses.events
|
||||
(:require
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.data-store :as data-store]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn save-address
|
||||
[{:keys [db]}
|
||||
[{:keys [address name customization-color on-success on-error chain-short-names ens]
|
||||
:or {on-success (fn [])
|
||||
on-error (fn [])
|
||||
name ""
|
||||
customization-color constants/account-default-customization-color
|
||||
ens ""
|
||||
;; the chain short names should be a string like eth: or eth:arb:oeth:
|
||||
chain-short-names (str constants/mainnet-short-name ":")}}]]
|
||||
(let [test-networks-enabled? (boolean (get-in db [:profile/profile :test-networks-enabled?]))
|
||||
address-to-save {:address address
|
||||
:name name
|
||||
:colorId customization-color
|
||||
:ens ens
|
||||
:isTest test-networks-enabled?
|
||||
:chainShortNames chain-short-names}]
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "wakuext_upsertSavedAddress"
|
||||
:params [address-to-save]
|
||||
:on-success on-success
|
||||
:on-error on-error}]]]}))
|
||||
|
||||
(rf/reg-event-fx :wallet/save-address save-address)
|
||||
|
||||
(defn get-saved-addresses-success
|
||||
[{:keys [db]} [raw-saved-addresses]]
|
||||
(let [saved-addresses (reduce
|
||||
(fn [result {:keys [address test?] :as saved-address}]
|
||||
(assoc-in result [(if test? :test :prod) address] saved-address))
|
||||
{:test {}
|
||||
:prod {}}
|
||||
(data-store/rpc->saved-addresses raw-saved-addresses))]
|
||||
{:db (assoc-in db [:wallet :saved-addresses] saved-addresses)}))
|
||||
|
||||
(rf/reg-event-fx :wallet/get-saved-addresses-success get-saved-addresses-success)
|
||||
|
||||
(defn saved-addresses-rpc-error
|
||||
[_ [action error]]
|
||||
(log/warn (str "[wallet] [saved-addresses] Failed to " action)
|
||||
{:error error}))
|
||||
|
||||
(rf/reg-event-fx :wallet/saved-addresses-rpc-error saved-addresses-rpc-error)
|
||||
|
||||
(defn get-saved-addresses
|
||||
[_]
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "wakuext_getSavedAddresses"
|
||||
:on-success [:wallet/get-saved-addresses-success]
|
||||
:on-error [:wallet/saved-addresses-rpc-error :get-saved-addresses]}]]]})
|
||||
|
||||
(rf/reg-event-fx :wallet/get-saved-addresses get-saved-addresses)
|
||||
|
||||
(defn delete-saved-address-success
|
||||
[_ [toast-message]]
|
||||
{:fx [[:dispatch [:wallet/get-saved-addresses]]
|
||||
[:dispatch [:hide-bottom-sheet]]
|
||||
[:dispatch-later
|
||||
{:ms 100
|
||||
:dispatch [:toasts/upsert
|
||||
{:type :positive
|
||||
:theme :dark
|
||||
:text toast-message}]}]]})
|
||||
|
||||
(rf/reg-event-fx :wallet/delete-saved-address-success delete-saved-address-success)
|
||||
|
||||
(defn delete-saved-address-failed
|
||||
[_ [error]]
|
||||
{:fx [[:dispatch [:hide-bottom-sheet]]
|
||||
[:dispatch-later
|
||||
{:ms 100
|
||||
:dispatch [:toasts/upsert
|
||||
{:type :negative
|
||||
:theme :dark
|
||||
:text error}]}]]})
|
||||
|
||||
(rf/reg-event-fx :wallet/delete-saved-address-failed delete-saved-address-failed)
|
||||
|
||||
(defn delete-saved-address
|
||||
[{:keys [db]} [{:keys [address toast-message]}]]
|
||||
(let [test-networks-enabled? (boolean (get-in db [:profile/profile :test-networks-enabled?]))]
|
||||
{:fx [[:json-rpc/call
|
||||
[{:method "wakuext_deleteSavedAddress"
|
||||
:params [address test-networks-enabled?]
|
||||
:on-success [:wallet/delete-saved-address-success toast-message]
|
||||
:on-error [:wallet/delete-saved-address-failed]}]]]}))
|
||||
|
||||
(rf/reg-event-fx :wallet/delete-saved-address delete-saved-address)
|
|
@ -0,0 +1,152 @@
|
|||
(ns status-im.contexts.settings.wallet.saved-addresses.events-test
|
||||
(:require
|
||||
[cljs.test :refer-macros [deftest is testing]]
|
||||
matcher-combinators.test
|
||||
[status-im.contexts.settings.wallet.saved-addresses.events :as events]))
|
||||
|
||||
(deftest get-saved-addresses-test
|
||||
(testing "get saved addresses - dispatches RPC call"
|
||||
(let [cofx {:db {}}
|
||||
effects (events/get-saved-addresses cofx)
|
||||
result-fx (:fx effects)
|
||||
expected-fx [[:json-rpc/call
|
||||
[{:method "wakuext_getSavedAddresses"
|
||||
:on-success [:wallet/get-saved-addresses-success]
|
||||
:on-error [:wallet/saved-addresses-rpc-error :get-saved-addresses]}]]]]
|
||||
(is (match? expected-fx result-fx)))))
|
||||
|
||||
(def saved-address-1
|
||||
{:isTest false
|
||||
:address "0x1"
|
||||
:mixedcaseAddress "0x1"
|
||||
:chainShortNames "eth:arb1:oeth:"
|
||||
:name "Amy"
|
||||
:createdAt 1716826806
|
||||
:ens ""
|
||||
:colorId "purple"
|
||||
:removed false})
|
||||
|
||||
(def saved-address-2
|
||||
{:isTest true
|
||||
:address "0x2"
|
||||
:mixedcaseAddress "0x2"
|
||||
:chainShortNames "eth:arb1:oeth:"
|
||||
:name "Bob"
|
||||
:createdAt 1716826714
|
||||
:ens ""
|
||||
:colorId "blue"
|
||||
:removed false})
|
||||
|
||||
(deftest get-saved-addresses-success-test
|
||||
(testing "no saved addresses"
|
||||
(let [cofx {:db {}}
|
||||
effects (events/get-saved-addresses-success cofx nil)
|
||||
result-db (:db effects)
|
||||
expected-db {:wallet {:saved-addresses {:test {}
|
||||
:prod {}}}}]
|
||||
(is (match? expected-db result-db))))
|
||||
|
||||
(testing "one test saved address"
|
||||
(let [cofx {:db {}}
|
||||
effects (events/get-saved-addresses-success cofx [[saved-address-2]])
|
||||
result-db (:db effects)
|
||||
expected-db {:wallet {:saved-addresses
|
||||
{:test {"0x2" {:test? true
|
||||
:address "0x2"
|
||||
:mixedcase-address "0x2"
|
||||
:chain-short-names "eth:arb1:oeth:"
|
||||
:has-ens? false
|
||||
:network-preferences-names #{:arbitrum
|
||||
:optimism
|
||||
:mainnet}
|
||||
:name "Bob"
|
||||
:created-at 1716826714
|
||||
:ens ""
|
||||
:customization-color :blue
|
||||
:removed? false}}
|
||||
:prod {}}}}]
|
||||
(is (match? expected-db result-db))))
|
||||
|
||||
(testing "two saved addresses (test and prod)"
|
||||
(let [cofx {:db {}}
|
||||
effects (events/get-saved-addresses-success cofx [[saved-address-1 saved-address-2]])
|
||||
result-db (:db effects)
|
||||
expected-db {:wallet {:saved-addresses
|
||||
{:test {"0x2" {:test? true
|
||||
:address "0x2"
|
||||
:mixedcase-address "0x2"
|
||||
:chain-short-names "eth:arb1:oeth:"
|
||||
:network-preferences-names #{:arbitrum
|
||||
:optimism
|
||||
:mainnet}
|
||||
:has-ens? false
|
||||
:name "Bob"
|
||||
:created-at 1716826714
|
||||
:ens ""
|
||||
:customization-color :blue
|
||||
:removed? false}}
|
||||
:prod {"0x1" {:test? false
|
||||
:address "0x1"
|
||||
:mixedcase-address "0x1"
|
||||
:chain-short-names "eth:arb1:oeth:"
|
||||
:network-preferences-names #{:arbitrum
|
||||
:optimism
|
||||
:mainnet}
|
||||
:has-ens? false
|
||||
:name "Amy"
|
||||
:created-at 1716826806
|
||||
:ens ""
|
||||
:customization-color :purple
|
||||
:removed? false}}}}}]
|
||||
(is (match? expected-db result-db)))))
|
||||
|
||||
(deftest save-address-test
|
||||
(testing "save address - dispatches RPC call"
|
||||
(let [test-networks-enabled? false
|
||||
cofx {:db {:profile/profile {:test-networks-enabled?
|
||||
test-networks-enabled?}}}
|
||||
on-success [:some-success-event]
|
||||
on-error [:some-failure-event]
|
||||
name "Bob"
|
||||
address "0x3"
|
||||
ens "bobby.eth"
|
||||
customization-color :yellow
|
||||
chain-short-names "eth:arb1:oeth:"
|
||||
args {:on-success on-success
|
||||
:on-error on-error
|
||||
:name name
|
||||
:address address
|
||||
:customization-color customization-color
|
||||
:ens ens
|
||||
:chain-short-names chain-short-names}
|
||||
effects (events/save-address cofx [args])
|
||||
result-fx (:fx effects)
|
||||
expected-fx [[:json-rpc/call
|
||||
[{:method "wakuext_upsertSavedAddress"
|
||||
:params [{:address address
|
||||
:name name
|
||||
:colorId customization-color
|
||||
:ens ens
|
||||
:isTest test-networks-enabled?
|
||||
:chainShortNames chain-short-names}]
|
||||
:on-success on-success
|
||||
:on-error on-error}]]]]
|
||||
(is (match? expected-fx result-fx)))))
|
||||
|
||||
(deftest delete-saved-addresses-test
|
||||
(testing "delete saved addresses - dispatches RPC call"
|
||||
(let [test-networks-enabled? true
|
||||
cofx {:db {:profile/profile {:test-networks-enabled?
|
||||
test-networks-enabled?}}}
|
||||
address "0x1f69b0904160bf1ce98dabeaf9c2fe147569498d"
|
||||
toast-message "Saved addresses deleted successfully"
|
||||
args {:address address
|
||||
:toast-message toast-message}
|
||||
effects (events/delete-saved-address cofx [args])
|
||||
result-fx (:fx effects)
|
||||
expected-fx [[:json-rpc/call
|
||||
[{:method "wakuext_deleteSavedAddress"
|
||||
:params [address test-networks-enabled?]
|
||||
:on-success [:wallet/delete-saved-address-success toast-message]
|
||||
:on-error [:wallet/delete-saved-address-failed]}]]]]
|
||||
(is (match? expected-fx result-fx)))))
|
|
@ -0,0 +1,107 @@
|
|||
(ns status-im.contexts.settings.wallet.saved-addresses.sheets.address-options.view
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[status-im.common.not-implemented :as not-implemented]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.settings.wallet.saved-addresses.sheets.remove-address.view :as remove-address]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn view
|
||||
[{:keys [name full-address chain-short-names address] :as opts}]
|
||||
(let [open-send-flow (rn/use-callback
|
||||
#(rf/dispatch [:wallet/select-send-address
|
||||
{:address full-address
|
||||
:recipient full-address
|
||||
:stack-id :wallet-select-address
|
||||
:start-flow? true}])
|
||||
[full-address])
|
||||
open-eth-chain-explorer (rn/use-callback
|
||||
#(rf/dispatch [:wallet/navigate-to-chain-explorer
|
||||
{:address address
|
||||
:network constants/mainnet-network-name}])
|
||||
[address])
|
||||
open-arb-chain-explorer (rn/use-callback
|
||||
#(rf/dispatch [:wallet/navigate-to-chain-explorer
|
||||
{:address address
|
||||
:network constants/arbitrum-network-name}])
|
||||
[address])
|
||||
open-oeth-chain-explorer (rn/use-callback
|
||||
#(rf/dispatch [:wallet/navigate-to-chain-explorer
|
||||
{:address address
|
||||
:network constants/optimism-network-name}])
|
||||
[address])
|
||||
open-share (rn/use-callback
|
||||
#(rf/dispatch
|
||||
[:open-share
|
||||
{:options (if platform/ios?
|
||||
{:activityItemSources
|
||||
[{:placeholderItem {:type :text
|
||||
:content full-address}
|
||||
:item {:default {:type :text
|
||||
:content
|
||||
full-address}}
|
||||
:linkMetadata {:title full-address}}]}
|
||||
{:title full-address
|
||||
:message full-address
|
||||
:isNewTask true})}])
|
||||
[full-address])
|
||||
open-remove-confirmation-sheet (rn/use-callback
|
||||
#(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:theme :dark
|
||||
:shell? true
|
||||
:content (fn []
|
||||
[remove-address/view opts])}])
|
||||
[opts])]
|
||||
[quo/action-drawer
|
||||
[[{:icon :i/arrow-up
|
||||
:label (i18n/label :t/send-to-user {:user name})
|
||||
:blur? true
|
||||
:on-press open-send-flow
|
||||
:accessibility-label :send-to-user}
|
||||
{:icon :i/link
|
||||
:right-icon :i/external
|
||||
:label (i18n/label :t/view-address-on-etherscan)
|
||||
:blur? true
|
||||
:on-press open-eth-chain-explorer
|
||||
:accessibility-label :view-address-on-etherscan}
|
||||
(when (string/includes? chain-short-names constants/optimism-short-name)
|
||||
{:icon :i/link
|
||||
:right-icon :i/external
|
||||
:label (i18n/label :t/view-address-on-optimistic)
|
||||
:blur? true
|
||||
:on-press open-oeth-chain-explorer
|
||||
:accessibility-label :view-address-on-optimistic})
|
||||
(when (string/includes? chain-short-names constants/arbitrum-short-name)
|
||||
{:icon :i/link
|
||||
:right-icon :i/external
|
||||
:label (i18n/label :t/view-address-on-arbiscan)
|
||||
:blur? true
|
||||
:on-press open-arb-chain-explorer
|
||||
:accessibility-label :view-address-on-arbiscan})
|
||||
{:icon :i/share
|
||||
:on-press open-share
|
||||
:label (i18n/label :t/share-address)
|
||||
:blur? true
|
||||
:accessibility-label :share-saved-address}
|
||||
{:icon :i/qr-code
|
||||
:label (i18n/label :t/show-address-qr)
|
||||
:blur? true
|
||||
:on-press not-implemented/alert
|
||||
:accessibility-label :show-address-qr-code}
|
||||
{:icon :i/edit
|
||||
:label (i18n/label :t/edit-account)
|
||||
:blur? true
|
||||
:on-press not-implemented/alert
|
||||
:accessibility-label :edit-saved-address}
|
||||
{:icon :i/delete
|
||||
:label (i18n/label :t/remove-address)
|
||||
:blur? true
|
||||
:on-press open-remove-confirmation-sheet
|
||||
:danger? true
|
||||
:accessibility-label :remove-saved-address
|
||||
:add-divider? true}]]]))
|
|
@ -0,0 +1,39 @@
|
|||
(ns status-im.contexts.settings.wallet.saved-addresses.sheets.remove-address.view
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- hide-bottom-sheet
|
||||
[]
|
||||
(rf/dispatch [:hide-bottom-sheet]))
|
||||
|
||||
(defn view
|
||||
[{:keys [name address customization-color]}]
|
||||
(let [on-press-remove (rn/use-callback
|
||||
#(rf/dispatch [:wallet/delete-saved-address
|
||||
{:address address
|
||||
:toast-message (i18n/label :t/saved-address-removed)}])
|
||||
[address])]
|
||||
[:<>
|
||||
[quo/drawer-top
|
||||
{:title (i18n/label :t/remove-saved-address)
|
||||
:customization-color customization-color
|
||||
:type :context-tag
|
||||
:blur? true
|
||||
:context-tag-type :wallet-user
|
||||
:full-name name}]
|
||||
[quo/text
|
||||
{:style {:padding-horizontal 20
|
||||
:margin-bottom 8}}
|
||||
(i18n/label :t/remove-saved-address-description {:name name})]
|
||||
[quo/bottom-actions
|
||||
{:actions :two-actions
|
||||
:blur? true
|
||||
:button-one-label (i18n/label :t/remove)
|
||||
:button-one-props {:on-press on-press-remove
|
||||
:type :danger}
|
||||
:button-two-label (i18n/label :t/cancel)
|
||||
:button-two-props {:on-press hide-bottom-sheet
|
||||
:type :grey}}]]))
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
(def title-container
|
||||
{:padding-horizontal 20
|
||||
:margin-top 12})
|
||||
:margin-top 12
|
||||
:margin-bottom 16})
|
||||
|
||||
(defn page-wrapper
|
||||
[inset-top]
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
(ns status-im.contexts.settings.wallet.saved-addresses.view
|
||||
(:require [quo.core :as quo]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.resources :as resources]
|
||||
[status-im.contexts.settings.wallet.saved-addresses.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im.common.resources :as resources]
|
||||
[status-im.contexts.settings.wallet.saved-addresses.sheets.address-options.view :as address-options]
|
||||
[status-im.contexts.settings.wallet.saved-addresses.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn empty-state
|
||||
(defn- empty-state
|
||||
[]
|
||||
(let [theme (quo.theme/use-theme)]
|
||||
[quo/empty-state
|
||||
|
@ -17,12 +19,51 @@
|
|||
:image (resources/get-themed-image :sweating-man theme)
|
||||
:container-style style/empty-container-style}]))
|
||||
|
||||
(defn- saved-address
|
||||
[{:keys [name address chain-short-names customization-color has-ens? ens]}]
|
||||
(let [full-address (str chain-short-names address)
|
||||
on-press-saved-address (rn/use-callback
|
||||
#(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:theme :dark
|
||||
:shell? true
|
||||
:content (fn []
|
||||
[address-options/view
|
||||
{:address address
|
||||
:chain-short-names chain-short-names
|
||||
:full-address full-address
|
||||
:name name
|
||||
:customization-color customization-color}])}])
|
||||
[address chain-short-names full-address name customization-color])]
|
||||
[quo/saved-address
|
||||
{:user-props {:name name
|
||||
:address full-address
|
||||
:ens (when has-ens? ens)
|
||||
:customization-color customization-color
|
||||
:blur? true}
|
||||
:container-style {:margin-horizontal 8}
|
||||
:on-press on-press-saved-address}]))
|
||||
|
||||
(defn- header
|
||||
[{:keys [title]}]
|
||||
[quo/divider-label
|
||||
{:tight? true
|
||||
:blur? true}
|
||||
title])
|
||||
|
||||
(defn- footer
|
||||
[]
|
||||
[rn/view {:height 8}])
|
||||
|
||||
(defn- navigate-back
|
||||
[]
|
||||
(rf/dispatch [:navigate-back]))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [inset-top (safe-area/get-top)
|
||||
customization-color (rf/sub [:profile/customization-color])
|
||||
saved-addresses []
|
||||
navigate-back (rn/use-callback #(rf/dispatch [:navigate-back]))]
|
||||
saved-addresses (rf/sub [:wallet/grouped-saved-addresses])]
|
||||
[quo/overlay
|
||||
{:type :shell
|
||||
:container-style (style/page-wrapper inset-top)}
|
||||
|
@ -38,5 +79,13 @@
|
|||
:right :action
|
||||
:customization-color customization-color
|
||||
:icon :i/add}]]
|
||||
(when-not (seq saved-addresses)
|
||||
[empty-state])]))
|
||||
[rn/section-list
|
||||
{:key-fn :title
|
||||
:sticky-section-headers-enabled false
|
||||
:render-section-header-fn header
|
||||
:render-section-footer-fn footer
|
||||
:sections saved-addresses
|
||||
:render-fn saved-address
|
||||
:separator [rn/view {:style {:height 4}}]
|
||||
:content-container-style {:flex-grow 1}
|
||||
:empty-component [empty-state]}]]))
|
||||
|
|
|
@ -21,10 +21,11 @@
|
|||
: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}])
|
||||
(when (ff/enabled? ::ff/settings.saved-addresses)
|
||||
{:title (i18n/label :t/saved-addresses)
|
||||
:blur? true
|
||||
:on-press open-saved-addresses-settings-modal
|
||||
:action :arrow})])
|
||||
|
||||
(defn basic-settings
|
||||
[]
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
[clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.utils.networks :as network-utils]
|
||||
[utils.money :as money]
|
||||
[utils.number :as utils.number]
|
||||
[utils.transforms :as transforms]))
|
||||
|
@ -136,3 +137,33 @@
|
|||
[keypairs]
|
||||
(let [renamed-data (sort-and-rename-keypairs keypairs)]
|
||||
(cske/transform-keys csk/->kebab-case-keyword renamed-data)))
|
||||
|
||||
(defn- network-short-names->full-names
|
||||
[short-names-string]
|
||||
(->> (string/split short-names-string constants/chain-id-separator)
|
||||
(map network-utils/short-name->network)
|
||||
(remove nil?)
|
||||
set))
|
||||
|
||||
(defn- add-keys-to-saved-address
|
||||
[saved-address]
|
||||
(-> saved-address
|
||||
(assoc :network-preferences-names
|
||||
(network-short-names->full-names (:chain-short-names saved-address)))
|
||||
(assoc :has-ens? (not (string/blank? (:ens saved-address))))))
|
||||
|
||||
(defn rpc->saved-address
|
||||
[saved-address]
|
||||
(-> saved-address
|
||||
(set/rename-keys {:chainShortNames :chain-short-names
|
||||
:isTest :test?
|
||||
:createdAt :created-at
|
||||
:colorId :customization-color
|
||||
:mixedcaseAddress :mixedcase-address
|
||||
:removed :removed?})
|
||||
(update :customization-color keyword)
|
||||
add-keys-to-saved-address))
|
||||
|
||||
(defn rpc->saved-addresses
|
||||
[saved-addresses]
|
||||
(map rpc->saved-address saved-addresses))
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
[status-im.constants :as constants]
|
||||
[status-im.contexts.settings.wallet.effects]
|
||||
[status-im.contexts.settings.wallet.events]
|
||||
[status-im.contexts.wallet.common.utils.external-links :as external-links]
|
||||
[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]
|
||||
|
@ -343,6 +344,14 @@
|
|||
{:fx [[:dispatch [:hide-bottom-sheet]]
|
||||
[:dispatch [:browser.ui/open-url (str explorer-link "/" address)]]]}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/navigate-to-chain-explorer
|
||||
(fn [{:keys [db]} [{:keys [network chain-id address]}]]
|
||||
(let [chain-id (or chain-id (network-utils/network->chain-id db network))
|
||||
explorer-link (external-links/get-explorer-url-by-chain-id chain-id)]
|
||||
{:fx [[:dispatch [:hide-bottom-sheet]]
|
||||
[:dispatch [:browser.ui/open-url (str explorer-link "/" address)]]]})))
|
||||
|
||||
(rf/reg-event-fx :wallet/reload
|
||||
(fn [_]
|
||||
{:fx [[:dispatch-n [[:wallet/get-wallet-token]]]]}))
|
||||
|
@ -374,7 +383,8 @@
|
|||
{:fx [[:dispatch [:wallet/start-wallet]]
|
||||
[:dispatch [:wallet/get-ethereum-chains]]
|
||||
[:dispatch [:wallet/get-accounts]]
|
||||
[:dispatch [:wallet/get-keypairs]]]}))
|
||||
[:dispatch [:wallet/get-keypairs]]
|
||||
[:dispatch [:wallet/get-saved-addresses]]]}))
|
||||
|
||||
(rf/reg-event-fx :wallet/share-account
|
||||
(fn [_ [{:keys [content title]}]]
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
(ns status-im.contexts.wallet.save-address.events
|
||||
(:require
|
||||
[status-im.constants :as constants]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/save-address
|
||||
(fn [_
|
||||
[{:keys [address name customization-color on-success on-error chain-short-names ens test?]
|
||||
:or {on-success (fn [])
|
||||
on-error (fn [])
|
||||
name ""
|
||||
ens ""
|
||||
test? false
|
||||
;; the chain short names should be a string like eth: or eth:arb:oeth:
|
||||
chain-short-names (str constants/mainnet-short-name ":")}}]]
|
||||
(let [address-to-save {:address address
|
||||
:name name
|
||||
:color-id customization-color
|
||||
:ens ens
|
||||
:is-test test?
|
||||
:chain-short-names chain-short-names}]
|
||||
{:json-rpc/call
|
||||
[{:method "wakuext_upsertSavedAddress"
|
||||
:params [address-to-save]
|
||||
:on-success on-success
|
||||
:on-error on-error}]})))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/get-saved-addresses-success
|
||||
(fn [{:keys [db]} [saved-addresses]]
|
||||
{:db (assoc-in db [:wallet :saved-addresses] saved-addresses)}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/get-saved-addresses-error
|
||||
(fn [{:keys [db]} [err]]
|
||||
{:db (assoc db [:wallet :get-saved-addresses-error] err)}))
|
||||
|
||||
(rf/reg-event-fx
|
||||
:wallet/get-saved-addresses
|
||||
(fn [_ _]
|
||||
{:json-rpc/call
|
||||
[{:method "wakuext_getSavedAddresses"
|
||||
:on-success [:wallet/get-saved-addresses-success]
|
||||
:on-error [:wallet/get-saved-addresses-error]}]}))
|
|
@ -28,6 +28,7 @@
|
|||
status-im.contexts.onboarding.events
|
||||
status-im.contexts.profile.events
|
||||
status-im.contexts.profile.settings.events
|
||||
status-im.contexts.settings.wallet.saved-addresses.events
|
||||
status-im.contexts.shell.qr-reader.events
|
||||
status-im.contexts.shell.share.events
|
||||
status-im.contexts.syncing.events
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
:FLAG_WALLET_SETTINGS_KEYPAIRS_AND_ACCOUNTS_ENABLED)
|
||||
::settings.network-settings (enabled-in-env?
|
||||
:FLAG_WALLET_SETTINGS_NETWORK_SETTINGS_ENABLED)
|
||||
::settings.saved-addresses (enabled-in-env?
|
||||
:FLAG_WALLET_SETTINGS_SAVED_ADDRESSES_ENABLED)
|
||||
::shell.jump-to (enabled-in-env? :ENABLE_JUMP_TO)
|
||||
::wallet.assets-modal-hide (enabled-in-env? :FLAG_ASSETS_MODAL_HIDE)
|
||||
::wallet.assets-modal-manage-tokens (enabled-in-env? :FLAG_ASSETS_MODAL_MANAGE_TOKENS)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
(ns status-im.subs.wallet.saved-addresses
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[re-frame.core :as rf]))
|
||||
|
||||
(rf/reg-sub
|
||||
|
@ -13,3 +14,21 @@
|
|||
(fn [wallet [address]]
|
||||
(some #(= address (:address %))
|
||||
(:saved-addresses wallet))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/saved-addresses-by-network-mode
|
||||
:<- [:wallet/saved-addresses]
|
||||
:<- [:profile/test-networks-enabled?]
|
||||
(fn [[saved-addresses test-networks-enabled?]]
|
||||
(vals (get saved-addresses (if test-networks-enabled? :test :prod)))))
|
||||
|
||||
(rf/reg-sub
|
||||
:wallet/grouped-saved-addresses
|
||||
:<- [:wallet/saved-addresses-by-network-mode]
|
||||
(fn [saved-addresses]
|
||||
(->> saved-addresses
|
||||
(sort-by :name)
|
||||
(group-by #(string/upper-case (first (:name %))))
|
||||
(map (fn [[k v]]
|
||||
{:title k
|
||||
:data v})))))
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
(ns status-im.subs.wallet.saved-addresses-test
|
||||
(:require [clojure.test :refer [is testing]]
|
||||
[re-frame.db :as rf-db]
|
||||
[test-helpers.unit :as h]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(def saved-addresses-db
|
||||
{:test {"0x1" {:test? true
|
||||
:address "0x1"
|
||||
:mixedcase-address "0x1"
|
||||
:chain-short-names "eth:arb1:oeth:"
|
||||
:has-ens? false
|
||||
:network-preferences-names #{:arbitrum
|
||||
:optimism
|
||||
:mainnet}
|
||||
:name "Alice"
|
||||
:created-at 1716826714
|
||||
:ens ""
|
||||
:customization-color :blue
|
||||
:removed? false}
|
||||
"0x2" {:test? true
|
||||
:address "0x2"
|
||||
:mixedcase-address "0x2"
|
||||
:chain-short-names "eth:"
|
||||
:has-ens? false
|
||||
:network-preferences-names #{:mainnet}
|
||||
:name "Bob"
|
||||
:created-at 1716828825
|
||||
:ens ""
|
||||
:customization-color :purple
|
||||
:removed? false}}
|
||||
:prod {"0x1" {:test? false
|
||||
:address "0x1"
|
||||
:mixedcase-address "0x1"
|
||||
:chain-short-names "eth:"
|
||||
:has-ens? false
|
||||
:network-preferences-names #{:mainnet}
|
||||
:name "Alice"
|
||||
:created-at 1716826745
|
||||
:ens ""
|
||||
:customization-color :blue
|
||||
:removed? false}
|
||||
"0x2" {:test? false
|
||||
:address "0x2"
|
||||
:mixedcase-address "0x2"
|
||||
:chain-short-names "eth:arb1:oeth:"
|
||||
:has-ens? false
|
||||
:network-preferences-names #{:arbitrum
|
||||
:optimism
|
||||
:mainnet}
|
||||
:name "Bob"
|
||||
:created-at 1716828561
|
||||
:ens ""
|
||||
:customization-color :purple
|
||||
:removed? false}}})
|
||||
|
||||
(def grouped-saved-addresses-data
|
||||
[{:title "A"
|
||||
:data
|
||||
[{:test? false
|
||||
:address "0x1"
|
||||
:mixedcase-address "0x1"
|
||||
:chain-short-names "eth:"
|
||||
:has-ens? false
|
||||
:network-preferences-names #{:mainnet}
|
||||
:name "Alice"
|
||||
:created-at 1716826745
|
||||
:ens ""
|
||||
:customization-color :blue
|
||||
:removed? false}]}
|
||||
{:title "B"
|
||||
:data
|
||||
[{:test? false
|
||||
:address "0x2"
|
||||
:mixedcase-address "0x2"
|
||||
:chain-short-names "eth:arb1:oeth:"
|
||||
:has-ens? false
|
||||
:network-preferences-names #{:arbitrum
|
||||
:optimism
|
||||
:mainnet}
|
||||
:name "Bob"
|
||||
:created-at 1716828561
|
||||
:ens ""
|
||||
:customization-color :purple
|
||||
:removed? false}]}])
|
||||
|
||||
(h/deftest-sub :wallet/saved-addresses-by-network-mode
|
||||
[sub-name]
|
||||
(testing "returns saved addresses by network mode"
|
||||
(swap! rf-db/app-db
|
||||
#(-> %
|
||||
(assoc-in [:wallet :saved-addresses] saved-addresses-db)
|
||||
(assoc-in [:profile/profile :test-networks-enabled?] true)))
|
||||
(is (= (vals (:test saved-addresses-db)) (rf/sub [sub-name])))))
|
||||
|
||||
(h/deftest-sub :wallet/grouped-saved-addresses
|
||||
[sub-name]
|
||||
(testing "returns saved addresses grouped by name"
|
||||
(swap! rf-db/app-db
|
||||
#(-> %
|
||||
(assoc-in [:wallet :saved-addresses] saved-addresses-db)
|
||||
(assoc-in [:profile/profile :test-networks-enabled?] false)))
|
||||
(is (= grouped-saved-addresses-data (rf/sub [sub-name])))))
|
|
@ -2658,5 +2658,13 @@
|
|||
"proceed-anyway": "Proceed anyway",
|
||||
"sending-to-unpreferred-networks": "Sending to unpreferred networks",
|
||||
"review-send": "Review send",
|
||||
"review-bridge": "Review bridge"
|
||||
"review-bridge": "Review bridge",
|
||||
"send-to-user": "Send to {{user}}",
|
||||
"view-address-on-etherscan": "View address on Etherscan",
|
||||
"view-address-on-optimistic": "View address on Optimistic",
|
||||
"view-address-on-arbiscan": "View address on Arbiscan",
|
||||
"saved-address-removed": "Saved address removed",
|
||||
"remove-address": "Remove address",
|
||||
"remove-saved-address": "Remove saved address",
|
||||
"remove-saved-address-description": "Transaction history relating to this address will no longer be labelled ‘{{name}}’."
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue