Connected dApps: a list with empty state, plus button, etc etc / fetching dApps / disconnecting dApps (#19943)

* Connected dApps screen with empty state and list of dApps and the ability to disconnect dapps

* Fix

* Smaller style fix

---------

Co-authored-by: Lungu Cristian <lungucristian95@gmail.com>
This commit is contained in:
Alexander 2024-05-30 16:47:30 +02:00 committed by GitHub
parent 2ebe8fd4c9
commit c03d91bcb0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 318 additions and 71 deletions

View File

@ -7,8 +7,8 @@
{:dapp {:avatar nil
:name "Coingecko"
:value "coingecko.com"}
:accessibility-label "dapp-coingecko"
:state :default
:action :icon
:blur? false
:customization-color :blue})
@ -20,6 +20,6 @@
(h/is-truthy (h/get-by-text (:value (:dapp props)))))
(h/test "on-press event"
(let [mock-fn (h/mock-fn)]
(h/render [dapp/view (assoc props :on-press-icon mock-fn)])
(h/fire-event :press (h/get-by-test-id "dapp-component-icon"))
(h/render [dapp/view (assoc props :on-press mock-fn)])
(h/fire-event :press (h/get-by-label-text "dapp-coingecko"))
(h/was-called mock-fn))))

View File

@ -1,24 +1,24 @@
(ns quo.components.list-items.dapp.view
(:require
[quo.components.icon :as icons]
[quo.components.list-items.dapp.style :as style]
[quo.components.markdown.text :as text]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.fast-image :as fast-image]))
(defn view
[{:keys [dapp action on-press on-press-icon] :as props}]
[{:keys [dapp on-press right-component accessibility-label] :as props}]
(let [theme (quo.theme/use-theme)
[pressed? set-pressed] (rn/use-state false)
on-press-in (rn/use-callback #(set-pressed true))
on-press-out (rn/use-callback #(set-pressed false))]
[rn/pressable
{:style (style/container (assoc props :pressed? pressed?))
:on-press on-press
:on-press-in on-press-in
:on-press-out on-press-out}
{:style (style/container (assoc props :pressed? pressed?))
:accessibility-label accessibility-label
:on-press (when on-press
(fn [] (on-press dapp)))
:on-press-in on-press-in
:on-press-out on-press-out}
[rn/view {:style style/container-info}
[fast-image/fast-image
{:source (:avatar dapp)
@ -34,13 +34,5 @@
:size :paragraph-2
:style (style/style-text-value theme)}
(:value dapp)]]]
(when (= action :icon)
[rn/pressable
{:on-press on-press-icon
:testID "dapp-component-icon"}
[icons/icon :i/options
{:color (colors/theme-colors
colors/neutral-50
colors/neutral-40
theme)
:accessibility-label :icon}]])]))
(when right-component
[right-component dapp])]))

View File

@ -12,6 +12,10 @@
:justify-content :space-between
:align-items :center})
(def icon-container
{:flex-grow 1
:flex-basis 1})
(defn center-content-container
[centered?]
{:flex 1
@ -21,13 +25,19 @@
:justify-content (if centered? :center :flex-start)})
(def right-actions-container
{:flex-direction :row})
{:flex-direction :row
:justify-content :flex-end})
(def right-actions-spacing
{:width 12})
(def right-content-min-size
{:min-width 32 :min-height 32})
(defn right-content
[min-size?]
(merge
{:flex-grow 1
:flex-basis 1}
(when min-size?
{:min-height 32})))
(def token-logo
{:width 16 :height 16})

View File

@ -30,16 +30,17 @@
& children]
(into [rn/view {:style (style/container margin-top)}
(when icon-name
[button/button
{:type (button-type background)
:icon-only? true
:size 32
:on-press on-press
:background (if behind-overlay?
:blur
(when (button-properties/backgrounds background) background))
:accessibility-label accessibility-label}
icon-name])]
[rn/view {:style style/icon-container}
[button/button
{:type (button-type background)
:icon-only? true
:size 32
:on-press on-press
:background (if behind-overlay?
:blur
(when (button-properties/backgrounds background) background))
:accessibility-label accessibility-label}
icon-name]])]
children))
(defn- right-section-spacing [] [rn/view {:style style/right-actions-spacing}])
@ -88,7 +89,7 @@
[{:keys [background content max-actions min-size? support-account-switcher?
behind-overlay?]
:or {support-account-switcher? true}}]
[rn/view (when min-size? {:style style/right-content-min-size})
[rn/view (style/right-content min-size?)
(when (coll? content)
(into [rn/view {:style style/right-actions-container}]
(add-right-buttons-xf max-actions background behind-overlay? support-account-switcher?)

View File

@ -1,5 +1,6 @@
(ns quo.components.text-combinations.standard-title.component-spec
(:require [quo.components.text-combinations.standard-title.view :as standard-title]
(:require [quo.components.markdown.text :as text]
[quo.components.text-combinations.standard-title.view :as standard-title]
[test-helpers.component :as h]))
(h/describe "Text combinations - Standard title"
@ -48,4 +49,12 @@
:right :tag
:on-press on-press-fn}])
(h/fire-event :on-press (h/get-by-label-text :standard-title-tag))
(h/was-called-times on-press-fn 1)))))
(h/was-called-times on-press-fn 1))))
(h/describe "Custom content variant"
(h/test "Default render"
(h/render [standard-title/view
{:title "This is a title"
:right [text/text "Right"]}])
(h/is-truthy (h/get-by-text "This is a title"))
(h/is-truthy (h/get-by-text "Right")))))

View File

@ -66,4 +66,4 @@
:counter [right-counter props]
:action [right-action props]
:tag [right-tag props]
nil)])
right)])

View File

@ -28,10 +28,10 @@
:name "Coingecko"
:value "coingecko.com"}
:state :default
:action :icon
:blur? false
:customization-color :blue
:on-press-icon (fn [] (js/alert "Button pressed"))})]
:on-press (fn [{:keys [name]}]
(js/alert (str name " got pressed")))})]
(fn []
[preview/preview-container {:state state :descriptor descriptor}
[rn/view

View File

@ -15,6 +15,10 @@
:select-account {:content select-account/view}
nil))
(defn- on-dapps-press
[switcher-type]
(rf/dispatch [:show-bottom-sheet (get-bottom-sheet-args switcher-type)]))
(defn view
[{:keys [type on-press accessibility-label icon-name switcher-type margin-top]
:or {icon-name :i/close
@ -39,7 +43,6 @@
{:content-type :account-switcher
:customization-color color
:on-press #(rf/dispatch [:show-bottom-sheet
(get-bottom-sheet-args switcher-type)])
:on-press #(on-dapps-press switcher-type)
:emoji emoji
:type (when watch-only? :watch-only)}]}]))

View File

@ -0,0 +1,6 @@
(ns status-im.contexts.wallet.connected-dapps.disconnect-dapp.style)
(def content-wrapper
{:padding-vertical 8
:padding-horizontal 20
:gap 12})

View File

@ -0,0 +1,33 @@
(ns status-im.contexts.wallet.connected-dapps.disconnect-dapp.view
(:require
[quo.core :as quo]
[react-native.core :as rn]
[status-im.contexts.wallet.connected-dapps.disconnect-dapp.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn view
[{:keys [customization-color dapp on-disconnect]}]
(let [{:keys [avatar name]} dapp]
[:<>
[quo/drawer-top
{:type :context-tag
:context-tag-type :default
:full-name name
:profile-picture avatar
:title (i18n/label :t/disconnect-dapp)
:customization-color customization-color}]
[rn/view {:style style/content-wrapper}
[quo/text
{:size :paragraph-1}
(i18n/label :t/disconnect-dapp-confirmation {:dapp name})]]
[quo/bottom-actions
{:actions :two-actions
:button-one-label (i18n/label :t/disconnect)
:button-one-props {:type :danger
:accessibility-label :block-contact
:on-press on-disconnect}
:button-two-label (i18n/label :t/cancel)
:button-two-props {:type :grey
:accessibility-label :cancel
:on-press #(rf/dispatch [:hide-bottom-sheet])}}]]))

View File

@ -9,9 +9,35 @@
:padding-horizontal screen-padding
:margin-vertical 12})
(defn header-text
[bottom-padding?]
{:padding-horizontal screen-padding
:padding-top 12
:padding-bottom (when bottom-padding? 8)
:color colors/black})
(def header-wrapper
{:flex-direction :column
:height 96
:gap 8
:padding-horizontal 20
:padding-vertical 12})
(def account-details-wrapper
{:align-items :flex-start})
(def empty-container-style
{:justify-content :center
:flex 1
:margin-bottom 44})
(defn dapps-container
[bottom]
{:padding-horizontal 20
:padding-vertical 8
:margin-bottom bottom
:flex 1})
(defn dapps-list
[theme]
{:border-radius 16
:border-width 1
:border-color (colors/theme-colors colors/neutral-10 colors/neutral-80 theme)})
(defn separator
[theme]
{:height 1
:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80 theme)})

View File

@ -1,29 +1,129 @@
(ns status-im.contexts.wallet.connected-dapps.view
(:require
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[status-im.common.plus-button.view :as plus-button]
[status-im.common.resources :as resources]
[status-im.contexts.wallet.connected-dapps.disconnect-dapp.view :as disconnect-dapp]
[status-im.contexts.wallet.connected-dapps.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- on-disconnect
[wallet-account {:keys [name topic]}]
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch
[:wallet-connect/disconnect-dapp
{:topic topic
:on-success (fn []
(rf/dispatch [:wallet-connect/remove-pairing-by-topic topic])
(rf/dispatch [:toasts/upsert
{:id :dapp-disconnect-success
:type :positive
:text (i18n/label :t/disconnect-dapp-success
{:dapp name
:account (:name wallet-account)})}]))
:on-fail (fn []
(rf/dispatch [:toasts/upsert
{:id :dapp-disconnect-failure
:type :negative
:text (i18n/label :t/disconnect-dapp-fail
{:dapp name
:account (:name wallet-account)})}]))}]))
(defn- on-dapp-disconnect-press
[wallet-account dapp]
(rf/dispatch [:show-bottom-sheet
{:content (fn [] [disconnect-dapp/view
{:customization-color (:color wallet-account)
:dapp dapp
:on-disconnect #(on-disconnect wallet-account dapp)}])}]))
(defn- account-details
[{:keys [name emoji color]}]
(let [theme (quo.theme/use-theme)]
[rn/view {:style style/account-details-wrapper}
[quo/context-tag
{:theme theme
:type :account
:size 24
:account-name name
:emoji emoji
:customization-color color}]]))
(defn- header
[{:keys [title subtitle]}]
[:<>
[rn/view {:style style/header-container}
[quo/button
{:icon-only? true
:type :grey
:background :blur
:size 32
:accessibility-label :close-scan-qr-code
:on-press #(rf/dispatch [:navigate-back])}
:i/close]]
[quo/text
{:size :heading-1
:weight :semi-bold
:style (style/header-text (when subtitle true))}
title]])
[{:keys [title wallet-account on-close on-add]}]
(let [{:keys [color]} wallet-account]
[:<>
[rn/view {:style style/header-container}
[quo/button
{:icon-only? true
:type :grey
:background :blur
:size 32
:accessibility-label :connected-dapps-close
:on-press on-close}
:i/close]]
[rn/view {:style style/header-wrapper}
[quo/standard-title
{:title title
:accessibility-label :connected-dapps
:customization-color color
:right [plus-button/plus-button
{:on-press on-add
:accessibility-label :connected-dapps-add
:customization-color color}]}]
[rn/view {:flex 1}
[account-details wallet-account]]]]))
(defn view
[]
[rn/view {:style {:flex 1}}
[header {:title "Connected dApps"}]])
(let [{:keys [bottom]} (safe-area/get-insets)
{:keys [color] :as wallet-account} (rf/sub [:wallet/current-viewing-account])
pairings (rf/sub [:wallet-connect/pairings])
theme (quo.theme/use-theme)]
[rn/view {:flex 1}
[header
{:title (i18n/label :t/connected-dapps)
:wallet-account wallet-account
:on-close #(rf/dispatch [:navigate-back])
:on-add #(js/alert "Feature not implemented")}]
(if (empty? pairings)
[quo/empty-state
{:title (i18n/label :t/no-dapps)
:description (i18n/label :t/no-dapps-description)
:image (resources/get-themed-image :no-dapps theme)
:container-style style/empty-container-style}]
[rn/view (style/dapps-container bottom)
[rn/flat-list
{:data pairings
:always-bounce-vertical false
:content-container-style (style/dapps-list theme)
:render-fn (fn [{:keys [topic]
{:keys [icons name url]} :peerMetadata}]
[quo/dapp
{:dapp {:avatar (get icons 0)
:name name
:value url
:topic topic}
:accessibility-label (str "dapp-" topic)
:state :default
:action :icon
:blur? false
:customization-color color
:right-component (fn [dapp]
[rn/pressable
{:on-press (fn []
(on-dapp-disconnect-press
wallet-account
dapp))}
[quo/icon :i/disconnect
{:color (colors/theme-colors
colors/neutral-50
colors/neutral-40
theme)
:accessibility-label :icon}]])}])
:separator [rn/view {:style (style/separator theme)}]}]])]))

View File

@ -29,6 +29,14 @@
(js->clj :keywordize-keys true)
handler)))))
(rf/reg-fx
:effects.wallet-connect/fetch-pairings
(fn [{:keys [web3-wallet on-success on-fail]}]
(-> (.. web3-wallet -core -pairing)
(.getPairings)
(promesa/then on-success)
(promesa/catch on-fail))))
(rf/reg-fx
:effects.wallet-connect/pair
(fn [{:keys [web3-wallet url on-success on-fail]}]
@ -37,6 +45,21 @@
(promesa/then on-success)
(promesa/catch on-fail))))
(rf/reg-fx
:effects.wallet-connect/disconnect
(fn [{:keys [web3-wallet topic on-success on-fail]}]
(-> (.. web3-wallet -core -pairing)
(.disconnect (clj->js {:topic topic}))
(promesa/then on-success)
(promesa/catch on-fail))))
(rf/reg-fx
:effects.wallet-connect/fetch-active-sessions
(fn [{:keys [web3-wallet on-success on-fail]}]
(-> (.getActiveSessions web3-wallet)
(promesa/then on-success)
(promesa/catch on-fail))))
(rf/reg-fx
:effects.wallet-connect/approve-session
(fn [{:keys [web3-wallet proposal supported-namespaces on-success on-fail]}]

View File

@ -15,7 +15,13 @@
:wallet-connect/on-init-success
(fn [{:keys [db]} [web3-wallet]]
{:db (assoc db :wallet-connect/web3-wallet web3-wallet)
:fx [[:dispatch [:wallet-connect/register-event-listeners]]]}))
:fx [[:dispatch [:wallet-connect/register-event-listeners]]
[:effects.wallet-connect/fetch-pairings
{:web3-wallet web3-wallet
:on-fail #(log/error "Failed to get dApp pairings" {:error %})
:on-success (fn [data]
(rf/dispatch [:wallet-connect/set-pairings
(js->clj data :keywordize-keys true)]))}]]}))
(rf/reg-event-fx
:wallet-connect/register-event-listeners
@ -43,6 +49,29 @@
(fn [{:keys [db]}]
{:db (dissoc db :wallet-connect/current-proposal)}))
(rf/reg-event-fx
:wallet-connect/set-pairings
(fn [{:keys [db]} [pairings]]
{:db (assoc db :wallet-connect/pairings pairings)}))
(rf/reg-event-fx
:wallet-connect/remove-pairing-by-topic
(fn [{:keys [db]} [topic]]
{:db (update db
:wallet-connect/pairings
(fn [pairings]
(remove #(= (:topic %) topic) pairings)))}))
(rf/reg-event-fx
:wallet-connect/disconnect-dapp
(fn [{:keys [db]} [{:keys [topic on-success on-fail]}]]
(let [web3-wallet (get db :wallet-connect/web3-wallet)]
{:fx [[:effects.wallet-connect/disconnect
{:web3-wallet web3-wallet
:topic topic
:on-fail on-fail
:on-success on-success}]]})))
(rf/reg-event-fx
:wallet-connect/pair
(fn [{:keys [db]} [url]]
@ -53,6 +82,15 @@
:on-fail #(log/error "Failed to pair with dApp" {:error %})
:on-success #(log/info "dApp paired successfully")}]]})))
(rf/reg-event-fx
:wallet-connect/fetch-active-sessions
(fn [{:keys [db]}]
(let [web3-wallet (get db :wallet-connect/web3-wallet)]
{:fx [[:effects.wallet-connect/fetch-active-sessions
{:web3-wallet web3-wallet
:on-fail #(log/error "Failed to get active sessions" {:error %})
:on-success #(log/info "Got active sessions successfully" {:sessions %})}]]})))
(rf/reg-event-fx
:wallet-connect/approve-session
(fn [{:keys [db]}]
@ -68,11 +106,12 @@
;; - wallet account dapps -> account that is selected
address (-> accounts keys first)
formatted-address (str (first crosschain-ids) ":" address)
supported-namespaces (clj->js {:eip155
{:chains crosschain-ids
:methods constants/wallet-connect-supported-methods
:events constants/wallet-connect-supported-events
:accounts [formatted-address]}})]
supported-namespaces (clj->js
{:eip155
{:chains crosschain-ids
:methods constants/wallet-connect-supported-methods
:events constants/wallet-connect-supported-events
:accounts [formatted-address]}})]
{:fx [[:effects.wallet-connect/approve-session
{:web3-wallet web3-wallet
:proposal current-proposal

View File

@ -169,6 +169,7 @@
;;wallet-connect
(reg-root-key-sub :wallet-connect/web3-wallet :wallet-connect/web3-wallet)
(reg-root-key-sub :wallet-connect/current-proposal :wallet-connect/current-proposal)
(reg-root-key-sub :wallet-connect/pairings :wallet-connect/pairings)
;;biometrics
(reg-root-key-sub :biometrics :biometrics)

View File

@ -2412,6 +2412,7 @@
"keypair-title": "{{name}}'s default key pair",
"about": "About",
"no-permissions": "No permissions",
"connected-dapps": "Connected dApps",
"no-dapps": "No connected dApps",
"days": "Days",
"add-account": "Add account",
@ -2467,6 +2468,9 @@
"scan-sync-code-placeholder": "cs2:4FH...",
"visit-dapp": "Visit dApp",
"disconnect-dapp": "Disconnect dApp",
"disconnect-dapp-confirmation": "Are you sure you want to disconnect {{dapp}}?",
"disconnect-dapp-success": "{{dapp}} disconnected from {{account}}",
"disconnect-dapp-fail": "Failed to disconnect {{dapp}} from {{account}}",
"edit-account": "Edit account",
"share-account": "Share account",
"remove-account": "Remove account",