From 389ce6695ab5ec9e8eb42c03b254a665669d71e0 Mon Sep 17 00:00:00 2001 From: Shivek Khurana Date: Wed, 22 Sep 2021 05:45:21 +0530 Subject: [PATCH] =?UTF-8?q?NFT=20UI=20=F0=9F=96=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New UI and re-frame handlers for setting pfp from url Add fake chain-id, connect the pfp upload backend to UI Add toast on setting pfp, new-new-new UI Show assets based on selected network Add horzontal padding to traits card a11y ids and pr review changes Added emoji in toast, fix chain id nill issue Restore Podfile Fix fix podfile Add placeholder images for collections, hide option to set pfp when image url is empty or ends in mp4 or svg Improvise missing designs Fix paddings New nu placeholders Sort collections by name Kinda fix issue 4 Fix lint Signed-off-by: Shivek Khurana --- resources/images/ui/collectible-dark@2x.png | Bin 0 -> 8161 bytes resources/images/ui/collectible-dark@3x.png | Bin 0 -> 12527 bytes resources/images/ui/collectible@2x.png | Bin 0 -> 7292 bytes resources/images/ui/collectible@3x.png | Bin 0 -> 11597 bytes src/status_im/ethereum/json_rpc.cljs | 1 + src/status_im/multiaccounts/core.cljs | 14 + src/status_im/react_native/resources.cljs | 4 +- src/status_im/subs.cljs | 13 +- src/status_im/ui/components/accordion.cljs | 7 +- .../ui/components/toastable_highlight.cljs | 117 ++++++++ .../privacy_and_security_settings/views.cljs | 2 +- src/status_im/ui/screens/screens.cljs | 3 +- .../ui/screens/wallet/account/views.cljs | 141 +--------- .../ui/screens/wallet/collectibles/views.cljs | 256 ++++++++++++++++++ src/status_im/wallet/accounts/core.cljs | 1 + src/status_im/wallet/core.cljs | 41 ++- translations/en.json | 8 +- 17 files changed, 447 insertions(+), 161 deletions(-) create mode 100644 resources/images/ui/collectible-dark@2x.png create mode 100644 resources/images/ui/collectible-dark@3x.png create mode 100644 resources/images/ui/collectible@2x.png create mode 100644 resources/images/ui/collectible@3x.png create mode 100644 src/status_im/ui/components/toastable_highlight.cljs create mode 100644 src/status_im/ui/screens/wallet/collectibles/views.cljs diff --git a/resources/images/ui/collectible-dark@2x.png b/resources/images/ui/collectible-dark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fc0c066e5bff253c586627b492d4eb8e9308694d GIT binary patch literal 8161 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 literal 0 HcmV?d00001 diff --git a/resources/images/ui/collectible@3x.png b/resources/images/ui/collectible@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..daf70ecfcee6e1bf7f69b89312767b2aa96c0df7 GIT binary patch literal 11597 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 C?X0i> literal 0 HcmV?d00001 diff --git a/src/status_im/ethereum/json_rpc.cljs b/src/status_im/ethereum/json_rpc.cljs index e61a0e2a3d..929bb74c0c 100644 --- a/src/status_im/ethereum/json_rpc.cljs +++ b/src/status_im/ethereum/json_rpc.cljs @@ -134,6 +134,7 @@ "multiaccounts_getIdentityImages" {} "multiaccounts_getIdentityImage" {} "multiaccounts_storeIdentityImage" {} + "multiaccounts_storeIdentityImageFromURL" {} "multiaccounts_deleteIdentityImage" {} "wakuext_changeIdentityImageShowTo" {} "wakuext_createCommunity" {} diff --git a/src/status_im/multiaccounts/core.cljs b/src/status_im/multiaccounts/core.cljs index 68fa28a927..16b0f1d923 100644 --- a/src/status_im/multiaccounts/core.cljs +++ b/src/status_im/multiaccounts/core.cljs @@ -202,6 +202,20 @@ :type (name photo-quality-large)}]) (bottom-sheet/hide-bottom-sheet)))) +(fx/defn save-profile-picture-from-url + {:events [::save-profile-picture-from-url]} + [cofx url] + (let [key-uid (get-in cofx [:db :multiaccount :key-uid])] + (fx/merge cofx + {::json-rpc/call [{:method "multiaccounts_storeIdentityImageFromURL" + :params [key-uid url] + :on-error log/warn + :on-success #(re-frame/dispatch [::update-local-picture %])}]} + (bottom-sheet/hide-bottom-sheet)))) + +(comment + (re-frame/dispatch [::save-profile-picture-from-url "https://lh3.googleusercontent.com/XuKjNm3HydsaxbPkbpGs9YyCKhn5QQk5oDC8XF2jzmPyYXeZofxFtfUDZuQ3EVmacS_BlBKzbX2ypm37YNX3n1fDJA3WndeFcPsp7Z0=w600"])) + (fx/defn delete-profile-picture {:events [::delete-profile-picture]} [cofx name] diff --git a/src/status_im/react_native/resources.cljs b/src/status_im/react_native/resources.cljs index 36fe2b66ce..2ff16e14b1 100644 --- a/src/status_im/react_native/resources.cljs +++ b/src/status_im/react_native/resources.cljs @@ -6,9 +6,9 @@ :welcome (js/require "../resources/images/ui/welcome.jpg") :welcome-dark (js/require "../resources/images/ui/welcome-dark.jpg") :chat (js/require "../resources/images/ui/chat.jpg") + :chat-dark (js/require "../resources/images/ui/chat-dark.jpg") :wallet (js/require "../resources/images/ui/wallet.jpg") :browser (js/require "../resources/images/ui/browser.jpg") - :chat-dark (js/require "../resources/images/ui/chat-dark.jpg") :wallet-dark (js/require "../resources/images/ui/wallet-dark.jpg") :browser-dark (js/require "../resources/images/ui/browser-dark.jpg") :keys (js/require "../resources/images/ui/keys.jpg") @@ -43,6 +43,8 @@ :theme-light (js/require "../resources/images/ui/theme-light.png") :theme-system (js/require "../resources/images/ui/theme-system.png") :notifications (js/require "../resources/images/ui/notifications.png") + :collectible (js/require "../resources/images/ui/collectible.png") + :collectible-dark (js/require "../resources/images/ui/collectible-dark.png") :graph (js/require "../resources/images/ui/graph.png")}) (defn get-theme-image [k] diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index 1abdd4b897..fa4a4621c6 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -176,8 +176,10 @@ (reg-root-key-sub :wallet/transactions-management-enabled? :wallet/transactions-management-enabled?) (reg-root-key-sub :wallet/all-tokens :wallet/all-tokens) (reg-root-key-sub :wallet/collectible-collections :wallet/collectible-collections) +(reg-root-key-sub :wallet/fetching-collection-assets :wallet/fetching-collection-assets) (reg-root-key-sub :wallet/collectible-assets :wallet/collectible-assets) -(reg-root-key-sub :wallet/current-collectible-asset :wallet/current-collectible-asset) +(reg-root-key-sub :wallet/selected-collectible :wallet/selected-collectible) + ;;commands (reg-root-key-sub :commands/select-account :commands/select-account) @@ -1831,7 +1833,8 @@ :<- [:wallet/collectible-collections] (fn [all-collections [_ address]] (when address - (get all-collections (string/lower-case address) [])))) + (let [all-collections (get all-collections (string/lower-case address) [])] + (sort-by :name all-collections))))) (re-frame/reg-sub :wallet/collectible-assets-by-collection-and-address @@ -1839,6 +1842,12 @@ (fn [all-assets [_ address collectible-slug]] (get-in all-assets [address collectible-slug] []))) +(re-frame/reg-sub + :wallet/fetching-assets-by-collectible-slug + :<- [:wallet/fetching-collection-assets] + (fn [fetching-collection-assets [_ collectible-slug]] + (get fetching-collection-assets collectible-slug false))) + ;;ACTIVITY CENTER NOTIFICATIONS ======================================================================================== (defn- group-notifications-by-date diff --git a/src/status_im/ui/components/accordion.cljs b/src/status_im/ui/components/accordion.cljs index 8f0d7f33f7..fd00dddd4e 100644 --- a/src/status_im/ui/components/accordion.cljs +++ b/src/status_im/ui/components/accordion.cljs @@ -20,15 +20,18 @@ (let [opened? (reagent/atom false)] (fn [{:keys [title content icon opened disabled padding-vertical dropdown-margin-left + open-container-style on-open on-close] :or {padding-vertical 8 dropdown-margin-left 8 + open-container-style {} on-open #() on-close #()}}] (let [on-press #(do (apply (if @opened? on-close on-open) []) (swap! opened? not))] - [react/view {:padding-vertical padding-vertical} + [react/view (merge {:padding-vertical padding-vertical} + (when @opened? open-container-style)) (if (string? title) [quo/list-item {:title title @@ -40,7 +43,7 @@ :margin-right 14 :justify-content :space-between} title - [drop-down-icon {:opened? (or @opened? opened) + [drop-down-icon {:opened? (or @opened? opened) :dropdown-margin-left dropdown-margin-left}]]]) (when (or @opened? opened) content)])))) diff --git a/src/status_im/ui/components/toastable_highlight.cljs b/src/status_im/ui/components/toastable_highlight.cljs new file mode 100644 index 0000000000..a164e35e71 --- /dev/null +++ b/src/status_im/ui/components/toastable_highlight.cljs @@ -0,0 +1,117 @@ +(ns status-im.ui.components.toastable-highlight + "A wrapped touchable highlight that presents a toast when clicked" + (:require [reagent.core :as reagent] + [status-im.ui.components.animation :as animation] + [quo.design-system.colors :as colors] + [status-im.ui.components.react :as react])) + +(defn hide-cue-atom [anim-opacity anim-y cue-atom] + (animation/start + (animation/parallel + [(animation/timing + anim-opacity + {:toValue 0 + :duration 140 + :delay 1000 + :easing (.-ease ^js animation/easing) + :useNativeDriver true}) + (animation/timing + anim-y + {:toValue 0 + :duration 140 + :delay 1000 + :easing (.-ease ^js animation/easing) + :useNativeDriver true})]) + #(reset! cue-atom false))) + +(defn show-cue-atom [anim-opacity anim-y cue-atom y] + (when @cue-atom + (animation/start + (animation/parallel + [(animation/timing + anim-opacity + {:toValue 1 + :duration 140 + :easing (.-ease ^js animation/easing) + :useNativeDriver true}) + (animation/timing + anim-y + {:toValue y + :duration 140 + :easing (.-ease ^js animation/easing) + :useNativeDriver true})]) + #(hide-cue-atom anim-opacity anim-y cue-atom)))) + +(defn toast [anim-opacity anim-y width cue-atom label] + [react/animated-view + {:style + {:opacity anim-opacity + :transform [{:translateY anim-y}] + :max-width @width + :z-index (if @cue-atom 1 -1) + :height 34 + :position :absolute + :border-radius 8 + :align-self :center + :align-items :center + :justify-content :center + :shadow-offset {:width 0 :height 4} + :shadow-radius 12 + :elevation 8 + :shadow-opacity 1 + :shadow-color "rgba(0, 34, 51, 0.08)" + :background-color colors/white}} + [react/view + {:padding-horizontal 16 + :padding-vertical 7 + :border-radius 8 + :background-color colors/white + :shadow-offset {:width 0 :height 2} + :shadow-radius 4 + :shadow-opacity 1 + :shadow-color "rgba(0, 34, 51, 0.16)"} + [react/text + {:style + {:typography :main-medium + ;; line height specified here because of figma spec + :line-height 20 + :font-size 14}} + label]]]) + +(defn toastable-highlight-view + [{:keys [toast-label on-press + container-style]} + content] + (let [cue-atom (reagent/atom false) + width (reagent/atom 0) + height (reagent/atom 0) + anim-y (animation/create-value 0) + anim-opacity (animation/create-value 0)] + (reagent/create-class + {:reagent-render + (fn [{:keys []} _] + (let [press-fn #(when (not @cue-atom) + (reset! cue-atom true) + (show-cue-atom + anim-opacity + anim-y + cue-atom + (if (> @height 34) + (- (/ @height 2)) + (- (+ 17 @height)))) + (when on-press + (on-press)))] + [react/view + {:style (if container-style container-style {}) + :on-layout + #(do + (reset! width (-> ^js % .-nativeEvent .-layout .-width)) + (reset! height (-> ^js % .-nativeEvent .-layout .-height)))} + [toast anim-opacity anim-y width cue-atom toast-label] + [react/touchable-highlight + {:active-opacity (if @cue-atom 1 0.85) + :underlay-color colors/black + :on-press press-fn + :on-long-press press-fn} + [react/view {:background-color colors/white} + content]]]))}))) diff --git a/src/status_im/ui/screens/privacy_and_security_settings/views.cljs b/src/status_im/ui/screens/privacy_and_security_settings/views.cljs index b476864385..4595cfca4c 100644 --- a/src/status_im/ui/screens/privacy_and_security_settings/views.cljs +++ b/src/status_im/ui/screens/privacy_and_security_settings/views.cljs @@ -71,7 +71,7 @@ ((complement boolean) preview-privacy?)])}] (when config/collectibles-enabled? [quo/list-item {:size :small - :title (i18n/label :t/enable-opensea-nfts) + :title (i18n/label :t/display-collectibles) :container-margin-bottom 8 :active opensea-enabled? :accessory :switch diff --git a/src/status_im/ui/screens/screens.cljs b/src/status_im/ui/screens/screens.cljs index 5bfb1fec94..d5df196d21 100644 --- a/src/status_im/ui/screens/screens.cljs +++ b/src/status_im/ui/screens/screens.cljs @@ -55,6 +55,7 @@ [status-im.ui.screens.wallet.transactions.views :as wallet-transactions] [status-im.ui.screens.wallet.custom-tokens.views :as custom-tokens] [status-im.ui.screens.wallet.accounts.views :as wallet.accounts] + [status-im.ui.screens.wallet.collectibles.views :as wallet.collectibles] [status-im.ui.screens.wallet.account.views :as wallet.account] [status-im.ui.screens.wallet.add-new.views :as add-account] [status-im.ui.screens.wallet.account-settings.views :as account-settings] @@ -700,7 +701,7 @@ :insets {:bottom true} ;;TODO dynamic title :options {:topBar {:visible false}} - :component wallet.account/nft-details} + :component wallet.collectibles/nft-details-modal} ;My Status {:name :my-status diff --git a/src/status_im/ui/screens/wallet/account/views.cljs b/src/status_im/ui/screens/wallet/account/views.cljs index 396d0cd260..2068769a4d 100644 --- a/src/status_im/ui/screens/wallet/account/views.cljs +++ b/src/status_im/ui/screens/wallet/account/views.cljs @@ -6,8 +6,6 @@ [status-im.ui.components.animation :as animation] [quo.design-system.colors :as colors] [status-im.ui.components.icons.icons :as icons] - [status-im.ui.components.accordion :as accordion] - [status-im.react-native.resources :as resources] [quo.core :as quo] [status-im.ui.components.react :as react] [status-im.ui.components.topbar :as topbar] @@ -17,11 +15,8 @@ [status-im.ui.screens.wallet.accounts.views :as accounts] [status-im.ui.screens.wallet.buy-crypto.views :as buy-crypto] [status-im.ui.screens.wallet.transactions.views :as history] - [status-im.wallet.core :as wallet] [status-im.ui.components.tabs :as tabs] - [status-im.multiaccounts.update.core :as multiaccounts.update] - [status-im.ui.screens.wallet.components.views :as wallet.components] - [status-im.utils.handlers :refer [ - (for [i (range num-assets)] - ^{:key i} - [react/view {:style {:width "48%" - :margin-bottom 16}} - [react/view {:style {:flex 1 - :aspect-ratio 1 - :border-width 1 - :background-color colors/gray-transparent-10 - :border-color colors/gray-lighter - :border-radius 16}}]])]) - -(defn nft-assets [{:keys [num-assets address collectible-slug]}] - (let [assets ( - (for [collectible collection] - ^{:key (:slug collectible)} - [accordion/section - {:title - [react/view {:flex 1} - [quo/list-item - {:title (:name collectible) - :text-size :large - :icon - [wallet.components/token-icon {:style {:border-radius 40 - :overflow :hidden - :border-width 1 - :border-color "#EEF2F5"} - :source {:uri (:image_url collectible)}}] - :accessory :text - :accessory-text (:owned_asset_count collectible)}]] - :padding-vertical 0 - :dropdown-margin-left -12 - :on-open #(re-frame/dispatch [::wallet/fetch-collectible-assets-by-owner-and-collection - address - (:slug collectible) - (:owned_asset_count collectible)]) - :content [nft-assets {:address address - :num-assets (:owned_asset_count collectible) - :collectible-slug (:slug collectible)}]}])])) - -(defn nft-traits [traits] - [react/view {:flex 1 - :margin-bottom 24 - :flex-direction :row - :flex-wrap :wrap} - (for [trait traits] - ^{:key (:trait_type trait)} - [react/view {:style {:border-width 1 - :border-radius 12 - :padding-horizontal 8 - :padding-vertical 4 - :margin-right 8 - :margin-bottom 8 - :border-color colors/gray-lighter}} - [quo/text {:size :small - :color :secondary} - (:trait_type trait)] - [quo/text {} - (:value trait)]])]) - -(defn nft-details [] - (let [nft ( - [topbar/topbar - {:title (:name nft) - :subtitle (-> nft :collection :name) - :border-bottom false - :right-accessories - [{:icon :main-icons/browser - :on-press #(re-frame/dispatch [:browser.ui/open-url (:permalink nft)])}]}] - [react/scroll-view {:padding 16} - [react/image {:source {:uri (:image_url nft)} - :style {:width "100%" - :aspect-ratio 1 - :border-radius 4 - :border-width 1 - :border-color "#EEF2F5"}}] - [react/text {:style {:margin-top 24 - :margin-bottom 16}} - (:description nft)] - [nft-traits (:traits nft)]]])) - -(defn enable-opensea-view [] - [react/view {:style {:border-width 1 - :border-color colors/gray-lighter - :border-radius 8 - :margin 16}} - [react/view {:style {:padding 16}} - [react/image {:source (resources/get-theme-image :unfurl) - :style {:align-self :center - :width 132 - :height 94}}] - [quo/text {:size :small - :align :center - :style {:margin-top 6}} - (i18n/label :t/enable-opensea-nfts)] - [quo/text {:size :small - :color :secondary - :align :center - :style {:margin-top 2}} - (i18n/label :t/opensea-nfts-leak-metadata)]] - [quo/separator] - [quo/button {:on-press #(re-frame/dispatch [::multiaccounts.update/toggle-opensea-nfts-visiblity true]) - :type :secondary} - (i18n/label :enable)]]) - (views/defview assets-and-collections [address] (views/letsubs [{:keys [tokens]} [:wallet/visible-assets-with-values address] currency [:wallet/currency] @@ -257,10 +122,10 @@ (when config/collectibles-enabled? (cond (not opensea-enabled?) - [enable-opensea-view] + [collectibles.views/enable-opensea-view] (and opensea-enabled? (seq collectible-collection)) - [nft-collections address] + [collectibles.views/nft-collections address] :else [react/view {:align-items :center :margin-top 32} diff --git a/src/status_im/ui/screens/wallet/collectibles/views.cljs b/src/status_im/ui/screens/wallet/collectibles/views.cljs new file mode 100644 index 0000000000..d223e24ae4 --- /dev/null +++ b/src/status_im/ui/screens/wallet/collectibles/views.cljs @@ -0,0 +1,256 @@ +(ns status-im.ui.screens.wallet.collectibles.views + (:require [re-frame.core :as re-frame] + [clojure.string :as str] + [status-im.ui.components.react :as react] + [quo.core :as quo] + [status-im.utils.handlers :refer [ + (for [i (range num-assets)] + ^{:key i} + [react/view {:style {:width "48%" + :margin-bottom 16}} + [react/view {:style {:flex 1 + :aspect-ratio 1 + :border-width 1 + :background-color colors/gray-transparent-10 + :border-color colors/gray-lighter + :border-radius 16}}]])]) + +(defn nft-trait-card [trait] + [react/view {:style {:border-width 1 + :border-radius 12 + :margin-right 8 + :padding-vertical 4 + :padding-horizontal 8 + :border-color colors/gray-lighter}} + [quo/text {:size :small + :color :secondary} + (:trait_type trait)] + [quo/text {} + (:value trait)]]) + +(defn nft-traits-scroller [traits] + [react/scroll-view {:horizontal true + :deceleration-rate "fast" + :snap-to-alignment "left" + :shows-horizontal-scroll-indicator + false + :scroll-event-throttle 64 + :style {:padding-left 16 + :margin-vertical 16 + :padding-bottom 8}} + (for [trait traits] + ^{:key (:trait_type trait)} + [nft-trait-card trait]) + + ;; spacer + [react/view {:style {:height 40 + :width 40}}]]) + +(defn nft-details-modal [] + (let [nft ( nft :collection :name)] + + (if (is-image? nft) + [react/image {:source {:uri (:image_url nft)} + :style {:width "100%" + :margin-bottom 16 + :aspect-ratio 1 + :border-radius 4 + :border-width 1 + :border-color colors/gray-lighter}}] + [missing-image-placeholder]) + + [quo/text {:style {:margin-top 12}} + (:description nft)]] + + (when (seq (:traits nft)) + [nft-traits-scroller (:traits nft)]) + + ;; seperator + [react/view {:style {:border-bottom-width 1 + :padding-top 8 + :border-color colors/gray-lighter}}] + + ;; TODO : Enable txns + ;; [quo/list-item {:title (i18n/label :t/wallet-send) + ;; :icon :main-icons/send + ;; :accessibility-label + ;; :nft-send + ;; :theme :accent + ;; :on-press #()}] + + ;; TODO : What to do with share? + ;; Share links or share image? + ;; [quo/list-item {:title (i18n/label :t/share) + ;; :theme :accent + ;; :accessibility-label + ;; :nft-share + ;; :on-press #() + ;; :icon :main-icons/share}] + [quo/list-item {:title (i18n/label :t/view-on-opensea) + :theme :accent + :icon :main-icons/browser + :on-press #(re-frame/dispatch [:browser.ui/open-url (:permalink nft)])}] + (when (is-image? nft) + [toastable-highlight-view + ;; the last string is an emoji. It might not show up in all editors but its there + {:toast-label (str (i18n/label :profile-picture-updated)) " " "😎"} + [quo/list-item {:title (i18n/label :t/use-as-profile-picture) + :theme :accent + :on-press #(re-frame/dispatch + [::multiaccounts/save-profile-picture-from-url (:image_url nft)]) + :icon :main-icons/profile + :accessibility-label + :set-nft-as-pfp}]])])) + +(defn no-assets-error [] + [react/view {:style {:flex 1 + :justify-content :center + :padding 16 + :margin-vertical 16 + :align-items :center + :border-radius 16}} + [icons/icon :photo {:color colors/red}] + [quo/text {:color :secondary + :style {:magin-top 8}} + (i18n/label :t/no-collectibles)]]) + +(defn nft-assets [{:keys [num-assets address collectible-slug]}] + (let [assets ( OpenSea sometimes doesn't return an asset + ;; This condition handles it + (and (not fetching?) + (not (seq assets))) + [no-assets-error] + + (seq assets) + (for [asset assets] + ^{:key (:id asset)} + [react/touchable-opacity + {:style + {:width "48%" + :border-radius 16 + :margin-bottom 16} + :on-press #(re-frame/dispatch [::wallet/show-nft-details asset]) + :accessibility-label + :nft-asset} + (if (is-image? asset) + [react/image {:style {:flex 1 + :aspect-ratio 1 + :border-width 1 + :border-color colors/gray-lighter + :border-radius 16} + :source {:uri (:image_url asset)}}] + [missing-image-placeholder])]))])) + +(defn nft-collections [address] + (let [collection ( + (for [[index collectible] (map-indexed vector collection)] + ^{:key (:slug collectible)} + [accordion/section + {:title + [react/view {:flex 1} + [quo/list-item + {:title (:name collectible) + :text-size :large + :accessibility-label + (keyword (str "collection-" index)) + :icon (if (seq (:image_url collectible)) + [wallet.components/token-icon + {:style {:border-radius 40 + :overflow :hidden + :border-width 1 + :border-color colors/gray-lighter} + :source {:uri (:image_url collectible)}}] + :main-icons/photo) + :accessory :text + :accessory-text (:owned_asset_count collectible)}]] + :padding-vertical 0 + :dropdown-margin-left -12 + :open-container-style {:border-top-width 8 + :border-bottom-width 8 + :border-color colors/gray-lighter} + :on-open #(re-frame/dispatch [::wallet/fetch-collectible-assets-by-owner-and-collection + address + (:slug collectible) + (:owned_asset_count collectible)]) + :content [nft-assets {:address address + :num-assets (:owned_asset_count collectible) + :collectible-slug (:slug collectible)}]}])])) + +(defn enable-opensea-view [] + [react/view {:style {:padding 16}} + [react/view {:style {:border-color colors/gray-lighter + :border-width 1 + :align-self :center + :padding 4 + :border-radius 12}} + [react/image {:source (resources/get-theme-image :collectible) + :style {:align-self :center + :resize-mode :contain}}]] + [quo/text {:align :center + :style {:margin-vertical 16}} + (i18n/label :t/collectibles-leak-metadata)] + [react/view {:align-items :center} + [quo/button {:accessibility-label :enable-opensea-nft-visibility + :on-press + #(re-frame/dispatch + [::multiaccounts.update/toggle-opensea-nfts-visiblity true]) + :theme :main + :type :primary} + (i18n/label :display-collectibles)]] + [quo/text {:size :small + :color :secondary + :align :center + :style {:margin-top 10}} + (i18n/label :t/disable-later-in-settings)]]) diff --git a/src/status_im/wallet/accounts/core.cljs b/src/status_im/wallet/accounts/core.cljs index a915393329..6089cb130c 100644 --- a/src/status_im/wallet/accounts/core.cljs +++ b/src/status_im/wallet/accounts/core.cljs @@ -343,3 +343,4 @@ {:events [:wallet.accounts/share]} [_ address] {:list.selection/open-share {:message (eip55/address->checksum address)}}) + diff --git a/src/status_im/wallet/core.cljs b/src/status_im/wallet/core.cljs index 2473ec0ee6..ae1dec1b84 100644 --- a/src/status_im/wallet/core.cljs +++ b/src/status_im/wallet/core.cljs @@ -220,36 +220,49 @@ {:events [::fetch-collectibles-collection]} [{:keys [db]}] (let [addresses (map (comp string/lower-case :address) - (get db :multiaccount/accounts))] + (get db :multiaccount/accounts)) + chain-id (-> db + ethereum/current-network + ethereum/network->chain-id)] (when (get-in db [:multiaccount :opensea-enabled?]) {::json-rpc/call (map (fn [address] - {:method "wallet_getOpenseaCollectionsByOwner" - :params [address] - :on-error (fn [error] - (log/error "Unable to get Opensea collections" address error)) + {:method "wallet_getOpenseaCollectionsByOwner" + :params [chain-id address] + :on-error (fn [error] + (log/error "Unable to get Opensea collections" address error)) :on-success #(re-frame/dispatch [::collectibles-collection-fetch-success address %])}) addresses)}))) (fx/defn collectible-assets-fetch-success {:events [::collectible-assets-fetch-success]} [{:keys [db]} address collectible-slug assets] - {:db (assoc-in db [:wallet/collectible-assets address collectible-slug] assets)}) + {:db (-> db + (assoc-in [:wallet/fetching-collection-assets collectible-slug] false) + (assoc-in [:wallet/collectible-assets address collectible-slug] assets))}) + +(fx/defn collectibles-assets-fetch-error + {:events [::collectibles-assets-fetch-error]} + [{:keys [db]} collectible-slug] + {:db (assoc-in db [:wallet/fetching-collection-assets collectible-slug] false)}) (fx/defn fetch-collectible-assets-by-owner-and-collection {:events [::fetch-collectible-assets-by-owner-and-collection]} - [_ address collectible-slug limit] - {::json-rpc/call [{:method "wallet_getOpenseaAssetsByOwnerAndCollection" - :params [address collectible-slug limit] - :on-error (fn [error] - (log/error "Unable to get collectible assets" address error)) - :on-success #(re-frame/dispatch [::collectible-assets-fetch-success address collectible-slug %])}]}) + [{:keys [db]} address collectible-slug limit] + (let [chain-id (ethereum/network->chain-id (ethereum/current-network db))] + {:db (assoc-in db [:wallet/fetching-collection-assets collectible-slug] true) + ::json-rpc/call [{:method "wallet_getOpenseaAssetsByOwnerAndCollection" + :params [chain-id address collectible-slug limit] + :on-error (fn [error] + (log/error "Unable to get collectible assets" address error) + (re-frame/dispatch [::collectibles-assets-fetch-error collectible-slug])) + :on-success #(re-frame/dispatch [::collectible-assets-fetch-success address collectible-slug %])}]})) (fx/defn show-nft-details {:events [::show-nft-details]} [cofx asset] (fx/merge cofx - {:db (assoc (:db cofx) :wallet/current-collectible-asset asset)} - (navigation/navigate-to :nft-details {}))) + {:db (assoc (:db cofx) :wallet/selected-collectible asset)} + (navigation/open-modal :nft-details {}))) (defn rpc->token [tokens] (reduce (fn [acc {:keys [address] :as token}] diff --git a/translations/en.json b/translations/en.json index 6e9020a24e..c185989262 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1669,6 +1669,10 @@ "fee-options": "Suggested fee options", "fee-cap": "Fee cap", "tip-cap": "Tip cap", - "enable-opensea-nfts": "Enable loading NFTs from OpenSea ?", - "opensea-nfts-leak-metadata": "Loading NFTs from OpenSea shares metadata with third party services" + "collectibles-leak-metadata": "You can display your NFTs here. If you do, you will share your wallet and IP address", + "display-collectibles": "Display collectibles", + "disable-later-in-settings": "You can disable this later in Settings", + "use-as-profile-picture": "Use as profile picture", + "view-on-opensea": "View on OpenSea", + "profile-picture-updated": "Profile picture updated" }