[#17938] Collectibles per account (#18277)

* Update wallet events and subs to handle collectibles per account

Additionally,
 - Move collectibles related events to a new events namespace (`status-im.contexts.wallet.events.collectibles`).
 - Update tests to consider collectibles per account.

* Update collectibles tab to handle a current viewing account
This commit is contained in:
Ulises Manuel 2024-01-03 18:41:12 -06:00 committed by GitHub
parent fc591f303c
commit 4ff5e4da8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 178 additions and 143 deletions

View File

@ -10,7 +10,10 @@
(defn- view-internal
[{:keys [theme]}]
(let [collectible-list (rf/sub [:wallet/collectibles])]
(let [specific-address (rf/sub [:wallet/current-viewing-account-address])
collectible-list (if specific-address
(rf/sub [:wallet/collectibles-per-account specific-address])
(rf/sub [:wallet/all-collectibles]))]
(if (empty? collectible-list)
[empty-tab/view
{:title (i18n/label :t/no-collectibles)

View File

@ -5,6 +5,7 @@
[clojure.string :as string]
[react-native.background-timer :as background-timer]
[status-im.common.data-store.wallet :as data-store]
[status-im.contexts.wallet.events.collectibles]
[status-im.contexts.wallet.item-types :as item-types]
[status-im.contexts.wallet.temp :as temp]
[taoensso.timbre :as log]
@ -13,8 +14,7 @@
[utils.i18n :as i18n]
[utils.money :as money]
[utils.number]
[utils.re-frame :as rf]
[utils.transforms :as types]))
[utils.re-frame :as rf]))
(rf/reg-event-fx :wallet/show-account-created-toast
(fn [{:keys [db]} [address]]
@ -65,10 +65,7 @@
db
(data-store/rpc->accounts wallet-accounts))
:fx [[:dispatch [:wallet/get-wallet-token]]
[:dispatch
[:wallet/request-collectibles
{:start-at-index 0
:new-request? true}]]
[:dispatch [:wallet/request-collectibles {:start-at-index 0 :new-request? true}]]
(when new-account?
[:dispatch [:wallet/navigate-to-new-account navigate-to-account]])]})))
@ -224,121 +221,6 @@
data)}]
{:db (assoc db :wallet/networks network-data)})))
(def collectibles-request-batch-size 1000)
(defn displayable-collectible?
[collectible]
(let [{:keys [image-url animation-url]} (:collectible-data collectible)]
(or (not (string/blank? animation-url))
(not (string/blank? image-url)))))
(defn store-collectibles
[{:keys [db]} [collectibles]]
(let [stored-collectibles (get-in db [:wallet :collectibles])
displayable-collectibles (filter displayable-collectible? collectibles)]
{:db (assoc-in db
[:wallet :collectibles]
(reduce conj displayable-collectibles stored-collectibles))}))
(rf/reg-event-fx :wallet/store-collectibles store-collectibles)
(defn clear-stored-collectibles
[{:keys [db]}]
{:db (update db :wallet dissoc :collectibles)})
(rf/reg-event-fx :wallet/clear-stored-collectibles clear-stored-collectibles)
(defn store-last-collectible-details
[{:keys [db]} [collectible]]
{:db (assoc-in db
[:wallet :last-collectible-details]
collectible)})
(rf/reg-event-fx :wallet/store-last-collectible-details store-last-collectible-details)
(def collectible-data-types
{:unique-id 0
:header 1
:details 2
:community-header 3})
(def fetch-type
{:never-fetch 0
:always-fetch 1
:fetch-if-not-cached 2
:fetch-if-cache-old 3})
(def max-cache-age-seconds 3600)
(rf/reg-event-fx
:wallet/request-collectibles
(fn [{:keys [db]} [{:keys [start-at-index new-request?]}]]
(let [request-id 0
collectibles-filter nil
data-type (collectible-data-types :header)
fetch-criteria {:fetch-type (fetch-type :fetch-if-not-cached)
:max-cache-age-seconds max-cache-age-seconds}
request-params [request-id
[(chain/chain-id db)]
(keys (get-in db [:wallet :accounts]))
collectibles-filter
start-at-index
collectibles-request-batch-size
data-type
fetch-criteria]]
{:fx [[:json-rpc/call
[{:method "wallet_getOwnedCollectiblesAsync"
:params request-params
:on-success #()
:on-error (fn [error]
(log/error "failed to request collectibles"
{:event :wallet/request-collectibles
:error error
:params request-params}))}]]
(when new-request?
[:dispatch [:wallet/clear-stored-collectibles]])]})))
(rf/reg-event-fx :wallet/owned-collectibles-filtering-done
(fn [_ [{:keys [message]}]]
(let [response (cske/transform-keys csk/->kebab-case-keyword
(types/json->clj message))
{:keys [collectibles has-more offset]} response
start-at-index (+ offset (count collectibles))]
{:fx
[[:dispatch [:wallet/store-collectibles collectibles]]
(when has-more
[:dispatch
[:wallet/request-collectibles
{:start-at-index start-at-index}]])]})))
(rf/reg-event-fx :wallet/get-collectible-details
(fn [_ [collectible-id]]
(let [request-id 0
collectible-id-converted (cske/transform-keys csk/->PascalCaseKeyword collectible-id)
data-type (collectible-data-types :details)
request-params [request-id [collectible-id-converted] data-type]]
{:fx [[:json-rpc/call
[{:method "wallet_getCollectiblesByUniqueIDAsync"
:params request-params
:on-error (fn [error]
(log/error "failed to request collectible"
{:event :wallet/get-collectible-details
:error error
:params request-params}))}]]]})))
(rf/reg-event-fx :wallet/get-collectible-details-done
(fn [_ [{:keys [message]}]]
(let [response (cske/transform-keys csk/->kebab-case-keyword
(types/json->clj message))
{:keys [collectibles]} response
collectible (first collectibles)]
(if collectible
{:fx
[[:dispatch [:wallet/store-last-collectible-details collectible]]]}
(log/error "failed to get collectible details"
{:event :wallet/get-collectible-details-done
:response response})))))
(rf/reg-event-fx :wallet/find-ens
(fn [{:keys [db]} [input contacts chain-id cb]]
(let [result (if (empty? input)

View File

@ -0,0 +1,125 @@
(ns status-im.contexts.wallet.events.collectibles
(:require [camel-snake-kebab.core :as csk]
[camel-snake-kebab.extras :as cske]
[clojure.string :as string]
[re-frame.core :as rf]
[taoensso.timbre :as log]
[utils.ethereum.chain :as chain]
[utils.transforms :as types]))
(def collectible-data-types
{:unique-id 0
:header 1
:details 2
:community-header 3})
(def fetch-type
{:never-fetch 0
:always-fetch 1
:fetch-if-not-cached 2
:fetch-if-cache-old 3})
(def max-cache-age-seconds 3600)
(def collectibles-request-batch-size 1000)
(defn displayable-collectible?
[collectible]
(let [{:keys [image-url animation-url]} (:collectible-data collectible)]
(or (not (string/blank? animation-url))
(not (string/blank? image-url)))))
(defn- add-collectibles-to-accounts
[accounts collectibles]
(reduce (fn [acc {:keys [ownership] :as collectible}]
(->> ownership
(map :address) ; In ERC1155 tokens a collectible can be owned by multiple addresses.
(reduce (fn add-collectible-to-address [acc address]
(update-in acc [address :collectibles] conj collectible))
acc)))
accounts
collectibles))
(defn store-collectibles
[{:keys [db]} [collectibles]]
(let [displayable-collectibles (filter displayable-collectible? collectibles)]
{:db (update-in db [:wallet :accounts] add-collectibles-to-accounts displayable-collectibles)}))
(rf/reg-event-fx :wallet/store-collectibles store-collectibles)
(defn clear-stored-collectibles
[{:keys [db]}]
{:db (update-in db [:wallet :accounts] update-vals #(dissoc % :collectibles))})
(rf/reg-event-fx :wallet/clear-stored-collectibles clear-stored-collectibles)
(defn store-last-collectible-details
[{:keys [db]} [collectible]]
{:db (assoc-in db [:wallet :last-collectible-details] collectible)})
(rf/reg-event-fx :wallet/store-last-collectible-details store-last-collectible-details)
(rf/reg-event-fx
:wallet/request-collectibles
(fn [{:keys [db]} [{:keys [start-at-index new-request?]}]]
(let [request-id 0
collectibles-filter nil
data-type (collectible-data-types :header)
fetch-criteria {:fetch-type (fetch-type :fetch-if-not-cached)
:max-cache-age-seconds max-cache-age-seconds}
request-params [request-id
[(chain/chain-id db)]
(keys (get-in db [:wallet :accounts]))
collectibles-filter
start-at-index
collectibles-request-batch-size
data-type
fetch-criteria]]
{:fx [[:json-rpc/call
[{:method "wallet_getOwnedCollectiblesAsync"
:params request-params
:on-error (fn [error]
(log/error "failed to request collectibles"
{:event :wallet/request-collectibles
:error error
:params request-params}))}]]
(when new-request?
[:dispatch [:wallet/clear-stored-collectibles]])]})))
(rf/reg-event-fx
:wallet/owned-collectibles-filtering-done
(fn [_ [{:keys [message]}]]
(let [{:keys [has-more offset
collectibles]} (cske/transform-keys csk/->kebab-case-keyword (types/json->clj message))
start-at-index (+ offset (count collectibles))]
{:fx [[:dispatch [:wallet/store-collectibles collectibles]]
(when has-more
[:dispatch [:wallet/request-collectibles {:start-at-index start-at-index}]])]})))
(rf/reg-event-fx
:wallet/get-collectible-details
(fn [_ [collectible-id]]
(let [request-id 0
collectible-id-converted (cske/transform-keys csk/->PascalCaseKeyword collectible-id)
data-type (collectible-data-types :details)
request-params [request-id [collectible-id-converted] data-type]]
{:fx [[:json-rpc/call
[{:method "wallet_getCollectiblesByUniqueIDAsync"
:params request-params
:on-error (fn [error]
(log/error "failed to request collectible"
{:event :wallet/get-collectible-details
:error error
:params request-params}))}]]]})))
(rf/reg-event-fx
:wallet/get-collectible-details-done
(fn [_ [{:keys [message]}]]
(let [response (cske/transform-keys csk/->kebab-case-keyword
(types/json->clj message))
{:keys [collectibles]} response
collectible (first collectibles)]
(if collectible
{:fx [[:dispatch [:wallet/store-last-collectible-details collectible]]]}
(log/error "failed to get collectible details"
{:event :wallet/get-collectible-details-done
:response response})))))

View File

@ -2,7 +2,8 @@
(:require
[cljs.test :refer-macros [deftest is testing]]
matcher-combinators.test
[status-im.contexts.wallet.events :as events]))
[status-im.contexts.wallet.events :as events]
[status-im.contexts.wallet.events.collectibles :as collectibles]))
(def address "0x2f88d65f3cb52605a54a833ae118fb1363acccd2")
@ -35,27 +36,40 @@
[false {:collectible-data {:image-url nil :animation-url ""}}]
[false {:collectible-data {:image-url "" :animation-url ""}}]]]
(doseq [[result collection] expected-results]
(is (match? result (events/displayable-collectible? collection))))))
(is (match? result (collectibles/displayable-collectible? collection))))))
(testing "save-collectibles-request-details"
(let [db {:wallet {}}
collectibles [{:collectible-data {:image-url "https://..." :animation-url "https://..."}}
{:collectible-data {:image-url "" :animation-url "https://..."}}
{:collectible-data {:image-url "" :animation-url nil}}]
expected-db {:wallet {:collectibles [{:collectible-data
{:image-url "https://..." :animation-url "https://..."}}
{:collectible-data
{:image-url "" :animation-url "https://..."}}]}}
effects (events/store-collectibles {:db db} [collectibles])
result-db (:db effects)]
(let [db {:wallet {:accounts {"0x1" {}
"0x3" {}}}}
collectible-1 {:collectible-data {:image-url "https://..." :animation-url "https://..."}
:ownership [{:address "0x1"
:balance "1"}]}
collectible-2 {:collectible-data {:image-url "" :animation-url "https://..."}
:ownership [{:address "0x1"
:balance "1"}]}
collectible-3 {:collectible-data {:image-url "" :animation-url nil}
:ownership [{:address "0x2"
:balance "1"}]}
collectibles [collectible-1 collectible-2 collectible-3]
expected-db {:wallet {:accounts {"0x1" {:collectibles (list collectible-2 collectible-1)}
"0x2" {:collectibles (list collectible-3)}
"0x3" {}}}}
effects (collectibles/store-collectibles {:db db} [collectibles])
result-db (:db effects)]
(is (match? result-db expected-db)))))
(deftest clear-stored-collectibles
(let [db {:wallet {:collectibles [{:id 1} {:id 2}]}}]
(let [db {:wallet {:accounts {"0x1" {:collectibles [{:id 1} {:id 2}]}
"0x2" {"some other stuff" "with any value"
:collectibles [{:id 3}]}
"0x3" {}}}}]
(testing "clear-stored-collectibles"
(let [expected-db {:wallet {}}
effects (events/clear-stored-collectibles {:db db})
(let [expected-db {:wallet {:accounts {"0x1" {}
"0x2" {"some other stuff" "with any value"}
"0x3" {}}}}
effects (collectibles/clear-stored-collectibles {:db db})
result-db (:db effects)]
(is (match? result-db expected-db))))))
(deftest store-last-collectible-details
@ -65,6 +79,6 @@
:image-url "https://..."}
expected-db {:wallet {:last-collectible-details {:description "Pandaria"
:image-url "https://..."}}}
effects (events/store-last-collectible-details {:db db} [last-collectible])
effects (collectibles/store-last-collectible-details {:db db} [last-collectible])
result-db (:db effects)]
(is (match? result-db expected-db)))))

View File

@ -11,12 +11,24 @@
image-url))
(re-frame/reg-sub
:wallet/collectibles
:wallet/collectibles-per-account
:<- [:wallet]
(fn [wallet [_ address]]
(as-> wallet $
(get-in $ [:accounts address :collectibles])
(map (fn [{:keys [collectible-data] :as collectible}]
(assoc collectible :preview-url (preview-url collectible-data)))
$))))
(re-frame/reg-sub
:wallet/all-collectibles
:<- [:wallet]
(fn [wallet]
(map (fn [collectible]
(assoc collectible :preview-url (preview-url (:collectible-data collectible))))
(:collectibles wallet))))
(->> wallet
:accounts
(mapcat (comp :collectibles val))
(map (fn [{:keys [collectible-data] :as collectible}]
(assoc collectible :preview-url (preview-url collectible-data)))))))
(re-frame/reg-sub
:wallet/last-collectible-details

View File

@ -39,7 +39,6 @@
:<- [:wallet/ui]
:-> :tokens-loading?)
(rf/reg-sub
:wallet/current-viewing-account-address
:<- [:wallet]