chore(wallet): smooth collectible list loading transition
This commit is contained in:
parent
f17484f61b
commit
777b2bb8da
|
@ -10,12 +10,13 @@
|
|||
|
||||
:image - collection image
|
||||
:theme - keyword -> :light/:dark"
|
||||
[{:keys [image size on-load-end on-error] :or {size :size-24}}]
|
||||
[{:keys [image size on-load-start on-load-end on-error] :or {size :size-24}}]
|
||||
(let [theme (quo.theme/use-theme)]
|
||||
[rn/view {:style (style/collection-avatar-container theme size)}
|
||||
[fast-image/fast-image
|
||||
{:accessibility-label :collection-avatar
|
||||
:source image
|
||||
:on-load-start on-load-start
|
||||
:on-load-end on-load-end
|
||||
:on-error on-error
|
||||
:style (style/collection-avatar size)}]]))
|
||||
|
|
|
@ -5,10 +5,14 @@
|
|||
(def container-border-radius 12)
|
||||
(def card-image-padding-vertical 3)
|
||||
(def card-image-padding-horizontal 3)
|
||||
(def default-opacity-for-loader 1)
|
||||
(def default-opacity-for-image 0)
|
||||
|
||||
(defn fallback
|
||||
[{:keys [theme]}]
|
||||
{:background-color (colors/theme-colors colors/neutral-2_5 colors/neutral-90 theme)
|
||||
[{:keys [theme opacity]}]
|
||||
[{:opacity opacity}
|
||||
{:opacity default-opacity-for-image
|
||||
:background-color (colors/theme-colors colors/neutral-2_5 colors/neutral-90 theme)
|
||||
:border-style :dashed
|
||||
:border-color (colors/theme-colors colors/neutral-20 colors/neutral-80 theme)
|
||||
:border-width 1
|
||||
|
@ -16,7 +20,7 @@
|
|||
:width "100%"
|
||||
:aspect-ratio 1
|
||||
:align-items :center
|
||||
:justify-content :center})
|
||||
:justify-content :center}])
|
||||
|
||||
(def collectible-counter
|
||||
{:position :absolute
|
||||
|
@ -59,6 +63,15 @@
|
|||
(def card-detail-text
|
||||
{:flex 1})
|
||||
|
||||
(defn card-loader
|
||||
[opacity]
|
||||
[{:opacity opacity}
|
||||
{:position :absolute
|
||||
:opacity default-opacity-for-loader
|
||||
:left 8
|
||||
:align-items :center
|
||||
:flex-direction :row}])
|
||||
|
||||
(def image-view-container
|
||||
{:aspect-ratio 1
|
||||
:border-radius container-border-radius})
|
||||
|
@ -83,6 +96,7 @@
|
|||
(defn loading-image
|
||||
[theme]
|
||||
{:position :absolute
|
||||
:opacity default-opacity-for-loader
|
||||
:top 0
|
||||
:bottom 0
|
||||
:left 0
|
||||
|
@ -91,8 +105,20 @@
|
|||
:background-color (colors/theme-colors colors/white-70-blur colors/neutral-95-opa-70-blur theme)
|
||||
:z-index 2})
|
||||
|
||||
(defn loading-image-with-opacity
|
||||
[theme opacity]
|
||||
[{:opacity opacity}
|
||||
(loading-image theme)])
|
||||
|
||||
(defn avatar-container
|
||||
[loaded?]
|
||||
[opacity]
|
||||
[{:opacity opacity}
|
||||
{:flex 1
|
||||
:flex-direction :row
|
||||
:opacity (when-not loaded? 0)})
|
||||
:opacity default-opacity-for-image}])
|
||||
|
||||
(defn supported-file
|
||||
[opacity]
|
||||
[{:opacity opacity}
|
||||
{:aspect-ratio 1
|
||||
:opacity default-opacity-for-image}])
|
||||
|
|
|
@ -10,13 +10,54 @@
|
|||
[quo.foundations.gradients :as gradients]
|
||||
[quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[schema.core :as schema]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(def timing-options-out 650)
|
||||
(def timing-options-in 1000)
|
||||
(def first-load-time 500)
|
||||
(def cached-load-time 200)
|
||||
(def error-wait-time 800)
|
||||
|
||||
(defn on-load-end
|
||||
[{:keys [load-time set-state loader-opacity image-opacity]}]
|
||||
(reanimated/animate loader-opacity 0 timing-options-out)
|
||||
(reanimated/animate image-opacity 1 timing-options-in)
|
||||
(if (> load-time cached-load-time)
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(set-state (fn [prev-state]
|
||||
(assoc prev-state :image-loaded? true))))
|
||||
first-load-time)
|
||||
(set-state (fn [prev-state]
|
||||
(assoc prev-state :image-loaded? true)))))
|
||||
|
||||
(defn on-load-error
|
||||
[set-state]
|
||||
(js/setTimeout (fn []
|
||||
(set-state (fn [prev-state] (assoc prev-state :image-error? true))))
|
||||
error-wait-time))
|
||||
|
||||
(defn on-load-avatar
|
||||
[{:keys [load-time set-state loader-opacity avatar-opacity]}]
|
||||
(reanimated/animate loader-opacity 0 timing-options-out)
|
||||
(reanimated/animate avatar-opacity 1 timing-options-in)
|
||||
(if (> load-time cached-load-time)
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(set-state (fn [prev-state]
|
||||
(assoc prev-state :avatar-loaded? true))))
|
||||
first-load-time)
|
||||
(set-state (fn [prev-state]
|
||||
(assoc prev-state :avatar-loaded? true)))))
|
||||
|
||||
(defn- fallback-view
|
||||
[{:keys [label theme]}]
|
||||
[rn/view
|
||||
{:style (style/fallback {:theme theme})}
|
||||
[{:keys [label theme image-opacity]}]
|
||||
[reanimated/view
|
||||
{:style (style/fallback {:opacity image-opacity
|
||||
:theme theme})}
|
||||
[rn/view
|
||||
[icon/icon :i/sad {:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)}]]
|
||||
[rn/view {:style {:height 4}}]
|
||||
|
@ -34,76 +75,84 @@
|
|||
[rn/view {:style (style/loading-message theme)}])
|
||||
|
||||
(defn- loading-image
|
||||
[{:keys [theme gradient-color-index]}]
|
||||
[{:keys [theme gradient-color-index loader-opacity]}]
|
||||
[reanimated/view
|
||||
{:style (style/loading-image-with-opacity theme loader-opacity)}
|
||||
[gradients/view
|
||||
{:theme theme
|
||||
:container-style (style/loading-image theme)
|
||||
:color-index gradient-color-index}])
|
||||
:color-index gradient-color-index}]])
|
||||
|
||||
(defn- card-details
|
||||
[{:keys [community? avatar-image-src collectible-name theme state set-state]}]
|
||||
(let [loader-opacity (reanimated/use-shared-value 1)
|
||||
avatar-opacity (reanimated/use-shared-value 0)
|
||||
[load-time set-load-time] (rn/use-state (datetime/now))]
|
||||
[rn/view {:style style/card-details-container}
|
||||
(cond (not (:avatar-loaded? state))
|
||||
[rn/view {:style {:flex-direction :row}}
|
||||
[loading-square theme]
|
||||
[loading-message theme]]
|
||||
|
||||
community?
|
||||
[:<>
|
||||
[reanimated/view {:style (style/avatar-container avatar-opacity)}
|
||||
(if community?
|
||||
[preview-list/view
|
||||
{:type :communities
|
||||
:size :size-20}
|
||||
[avatar-image-src]]
|
||||
[rn/view {:style {:width 8}}]
|
||||
[text/text
|
||||
{:size :paragraph-1
|
||||
:weight :semi-bold
|
||||
:style style/card-detail-text}
|
||||
collectible-name]])
|
||||
|
||||
[rn/view
|
||||
{:style (style/avatar-container (:avatar-loaded? state))}
|
||||
[:<>
|
||||
[collection-avatar/view
|
||||
{:size :size-20
|
||||
:on-load-end #(set-state (fn [prev-state] (assoc prev-state :avatar-loaded? true)))
|
||||
:image avatar-image-src}]
|
||||
[rn/view {:style {:width 8}}]]
|
||||
:on-start #(set-load-time (fn [start-time] (- (datetime/now) start-time)))
|
||||
:on-load-end #(on-load-avatar {:set-state set-state
|
||||
:load-time load-time
|
||||
:loader-opacity loader-opacity
|
||||
:avatar-opacity avatar-opacity})
|
||||
:image avatar-image-src}])
|
||||
[rn/view {:style {:width 8}}]
|
||||
[text/text
|
||||
{:size :paragraph-1
|
||||
:weight :semi-bold
|
||||
:ellipsize-mode :tail
|
||||
:number-of-lines 1
|
||||
:style style/card-detail-text}
|
||||
collectible-name]]])
|
||||
collectible-name]]
|
||||
(when (not (:avatar-loaded? state))
|
||||
[reanimated/view {:style (style/card-loader loader-opacity)}
|
||||
[loading-square theme]
|
||||
[loading-message theme]])]))
|
||||
|
||||
(defn- card-view
|
||||
[{:keys [avatar-image-src collectible-name community? counter state set-state
|
||||
gradient-color-index image-src supported-file?]}]
|
||||
(let [theme (quo.theme/use-theme)]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
loader-opacity (reanimated/use-shared-value (if supported-file? 1 0))
|
||||
image-opacity (reanimated/use-shared-value (if supported-file? 0 1))
|
||||
[load-time set-load-time] (rn/use-state (datetime/now))]
|
||||
[rn/view {:style (style/card-view-container theme)}
|
||||
[rn/view {:style {:aspect-ratio 1}}
|
||||
(cond
|
||||
(:image-error? state)
|
||||
[fallback-view
|
||||
{:theme theme
|
||||
{:image-opacity image-opacity
|
||||
:theme theme
|
||||
:label (i18n/label :t/cant-fetch-info)}]
|
||||
|
||||
(not supported-file?)
|
||||
[fallback-view
|
||||
{:theme theme
|
||||
{:image-opacity image-opacity
|
||||
:theme theme
|
||||
:label (i18n/label :t/unsupported-file)}]
|
||||
|
||||
(not (:image-loaded? state))
|
||||
[loading-image
|
||||
{:theme theme
|
||||
{:loader-opacity loader-opacity
|
||||
:theme theme
|
||||
:gradient-color-index gradient-color-index}])
|
||||
(when supported-file?
|
||||
[rn/view {:style {:aspect-ratio 1}}
|
||||
[reanimated/view {:style (style/supported-file image-opacity)}
|
||||
[rn/image
|
||||
{:style style/image
|
||||
:on-load-end #(set-state (fn [prev-state] (assoc prev-state :image-loaded? true)))
|
||||
:on-error #(set-state (fn [prev-state] (assoc prev-state :image-error? true)))
|
||||
:on-load-start #(set-load-time (fn [start-time] (- (datetime/now) start-time)))
|
||||
:on-load-end #(on-load-end {:load-time load-time
|
||||
:set-state set-state
|
||||
:loader-opacity loader-opacity
|
||||
:image-opacity image-opacity})
|
||||
:on-error #(on-load-error set-state)
|
||||
:source image-src}]])]
|
||||
(when (and (:image-loaded? state) (not (:image-error? state)) counter)
|
||||
[collectible-counter/view
|
||||
|
@ -121,29 +170,39 @@
|
|||
(defn- image-view
|
||||
[{:keys [avatar-image-src community? counter state set-state
|
||||
gradient-color-index image-src supported-file?]}]
|
||||
(let [theme (quo.theme/use-theme)]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
loader-opacity (reanimated/use-shared-value (if supported-file? 1 0))
|
||||
image-opacity (reanimated/use-shared-value (if supported-file? 0 1))
|
||||
[load-time set-load-time] (rn/use-state (datetime/now))]
|
||||
[rn/view {:style style/image-view-container}
|
||||
(cond
|
||||
(:image-error? state)
|
||||
[fallback-view
|
||||
{:theme theme
|
||||
{:image-opacity image-opacity
|
||||
:theme theme
|
||||
:label (i18n/label :t/cant-fetch-info)}]
|
||||
|
||||
(not supported-file?)
|
||||
[fallback-view
|
||||
{:theme theme
|
||||
{:image-opacity image-opacity
|
||||
:theme theme
|
||||
:label (i18n/label :t/unsupported-file)}]
|
||||
|
||||
(not (:image-loaded? state))
|
||||
[loading-image
|
||||
{:theme theme
|
||||
{:loader-opacity loader-opacity
|
||||
:theme theme
|
||||
:gradient-color-index gradient-color-index}])
|
||||
(when supported-file?
|
||||
[rn/view {:style {:aspect-ratio 1}}
|
||||
[reanimated/view {:style (style/supported-file image-opacity)}
|
||||
[rn/image
|
||||
{:style style/image
|
||||
:on-load-end #(set-state (fn [prev-state] (assoc prev-state :image-loaded? true)))
|
||||
:on-error #(set-state (fn [prev-state] (assoc prev-state :image-error? true)))
|
||||
:on-load-start #(set-load-time (fn [start-time] (- (datetime/now) start-time)))
|
||||
:on-load-end #(on-load-end {:load-time load-time
|
||||
:set-state set-state
|
||||
:loader-opacity loader-opacity
|
||||
:image-opacity image-opacity})
|
||||
:on-error #(on-load-error set-state)
|
||||
:source image-src}]])
|
||||
(when (and (:image-loaded? state) (not (:image-error? state)) counter)
|
||||
[collectible-counter/view
|
||||
|
|
|
@ -105,23 +105,6 @@
|
|||
(def fast-create-community-enabled?
|
||||
(enabled? (get-config :FAST_CREATE_COMMUNITY_ENABLED "0")))
|
||||
|
||||
(def default-multiaccount
|
||||
{:preview-privacy? blank-preview?
|
||||
:wallet-legacy/visible-tokens {:mainnet #{:SNT}}
|
||||
:currency :usd
|
||||
:appearance 0
|
||||
:profile-pictures-show-to 2
|
||||
:profile-pictures-visibility 2
|
||||
:log-level log-level
|
||||
:webview-allow-permission-requests? false
|
||||
:opensea-enabled? false
|
||||
:link-previews-enabled-sites #{}
|
||||
:link-preview-request-enabled true})
|
||||
|
||||
(defn default-visible-tokens
|
||||
[chain]
|
||||
(get-in default-multiaccount [:wallet-legacy/visible-tokens chain]))
|
||||
|
||||
(def waku-nodes-config
|
||||
{:status.prod
|
||||
["enrtree://AL65EKLJAUXKKPG43HVTML5EFFWEZ7L4LOKTLZCLJASG4DSESQZEC@prod.status.nodes.status.im"]
|
||||
|
|
|
@ -11,6 +11,22 @@
|
|||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- on-collectible-press
|
||||
[{:keys [id]}]
|
||||
(rf/dispatch [:wallet/get-collectible-details id]))
|
||||
|
||||
(defn- on-collectible-long-press
|
||||
[{:keys [preview-url collectible-details]}]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn []
|
||||
[options-drawer/view
|
||||
{:name (:name collectible-details)
|
||||
:image (:uri preview-url)}])}]))
|
||||
|
||||
(defn- on-end-reached
|
||||
[]
|
||||
(rf/dispatch [:wallet/request-collectibles-for-current-viewing-account]))
|
||||
|
||||
(defn view
|
||||
[{:keys [selected-tab]}]
|
||||
(let [collectible-list (rf/sub
|
||||
|
@ -22,17 +38,9 @@
|
|||
:collectibles [collectibles/view
|
||||
{:collectibles collectible-list
|
||||
:current-account-address current-account-address
|
||||
:on-end-reached #(rf/dispatch
|
||||
[:wallet/request-collectibles-for-current-viewing-account])
|
||||
:on-collectible-press (fn [{:keys [id]}]
|
||||
(rf/dispatch [:wallet/get-collectible-details id]))
|
||||
:on-collectible-long-press (fn [{:keys [preview-url collectible-details]}]
|
||||
(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn []
|
||||
[options-drawer/view
|
||||
{:name (:name collectible-details)
|
||||
:image (:uri preview-url)}])}]))}]
|
||||
:on-end-reached on-end-reached
|
||||
:on-collectible-press on-collectible-press
|
||||
:on-collectible-long-press on-collectible-long-press}]
|
||||
:activity [activity/view]
|
||||
:permissions [empty-tab/view
|
||||
{:title (i18n/label :t/no-permissions)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
(ns status-im.contexts.wallet.collectible.utils
|
||||
(:require [status-im.config :as config]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.contexts.wallet.common.utils.networks :as network-utils]))
|
||||
[status-im.contexts.wallet.common.utils.networks :as network-utils]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn collectible-balance
|
||||
[collectible]
|
||||
|
@ -23,7 +24,7 @@
|
|||
(if (supported-collectible-types collectible-type)
|
||||
true
|
||||
(do
|
||||
(println "unsupoorted collectible file type" collectible-type)
|
||||
(log/debug "unsupported collectible file type:" (or collectible-type "Unknown type"))
|
||||
false)))
|
||||
|
||||
(defn total-owned-collectible
|
||||
|
|
|
@ -4,3 +4,7 @@
|
|||
(def list-container-style
|
||||
{:margin-horizontal 12
|
||||
:padding-bottom constants/floating-shell-button-height})
|
||||
|
||||
(def collectible-container
|
||||
{:padding 8
|
||||
:flex 0.5})
|
||||
|
|
|
@ -9,10 +9,14 @@
|
|||
[status-im.contexts.wallet.common.empty-tab.view :as empty-tab]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(defn- render-fn
|
||||
[{:keys [preview-url collection-data ownership collectible-data] :as collectible} index address
|
||||
on-press on-long-press]
|
||||
(let [total-owned (utils/total-owned-collectible ownership address)]
|
||||
(defn- collectible-item
|
||||
[{:keys [preview-url collection-data collectible-data total-owned on-press on-long-press]
|
||||
:as collectible}
|
||||
index]
|
||||
(let [on-press-fn (rn/use-callback #(when on-press
|
||||
(on-press collectible)))
|
||||
on-long-press-fn (rn/use-callback #(when on-long-press
|
||||
(on-long-press collectible)))]
|
||||
[quo/collectible-list-item
|
||||
{:type :card
|
||||
:image-src (:uri preview-url)
|
||||
|
@ -21,18 +25,15 @@
|
|||
:supported-file? (utils/supported-file? (:animation-media-type collectible-data))
|
||||
:gradient-color-index (keyword (str "gradient-" (inc (mod index 5))))
|
||||
:counter (utils/collectible-owned-counter total-owned)
|
||||
:container-style {:padding 8
|
||||
:width "50%"}
|
||||
:on-press #(when on-press
|
||||
(on-press collectible))
|
||||
:on-long-press #(when on-long-press
|
||||
(on-long-press collectible))}]))
|
||||
:container-style style/collectible-container
|
||||
:on-press on-press-fn
|
||||
:on-long-press on-long-press-fn}]))
|
||||
|
||||
(defn view
|
||||
[{:keys [collectibles filtered? on-collectible-press on-end-reached current-account-address
|
||||
on-collectible-long-press]}]
|
||||
(let [no-results-match-query? (and filtered? (empty? collectibles))
|
||||
theme (quo.theme/use-theme)]
|
||||
[{:keys [collectibles filtered? on-end-reached
|
||||
on-collectible-press current-account-address on-collectible-long-press]}]
|
||||
(let [theme (quo.theme/use-theme)
|
||||
no-results-match-query? (and filtered? (empty? collectibles))]
|
||||
(cond
|
||||
no-results-match-query?
|
||||
[rn/view {:style {:flex 1 :justify-content :center}}
|
||||
|
@ -48,17 +49,25 @@
|
|||
:image (resources/get-themed-image :no-collectibles theme)}]
|
||||
|
||||
:else
|
||||
;; TODO: https://github.com/status-im/status-mobile/issues/20137
|
||||
;; 1. If possible, move `collectibles-data` calculation to a subscription
|
||||
;; 2. Optimization: do not recalculate all the collectibles, process only the new ones
|
||||
(let [collectibles-data (map-indexed (fn [index {:keys [ownership] :as collectible}]
|
||||
(assoc collectible
|
||||
:total-owned (utils/total-owned-collectible
|
||||
ownership
|
||||
current-account-address)
|
||||
:on-long-press on-collectible-long-press
|
||||
:on-press on-collectible-press
|
||||
:collectible-index index))
|
||||
collectibles)]
|
||||
[rn/flat-list
|
||||
{:data collectibles
|
||||
{:data collectibles-data
|
||||
:style {:flex 1}
|
||||
:content-container-style style/list-container-style
|
||||
:window-size 11
|
||||
:num-columns 2
|
||||
:render-fn (fn [item index]
|
||||
(render-fn item
|
||||
index
|
||||
current-account-address
|
||||
on-collectible-press
|
||||
on-collectible-long-press))
|
||||
:render-fn collectible-item
|
||||
:on-end-reached on-end-reached
|
||||
:on-end-reached-threshold 4}])))
|
||||
:key-fn :collectible-index
|
||||
:on-end-reached-threshold 4}]))))
|
||||
|
|
|
@ -8,6 +8,22 @@
|
|||
[status-im.contexts.wallet.home.tabs.style :as style]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- on-collectible-long-press
|
||||
[{:keys [preview-url collectible-details id]}]
|
||||
(let [chain-id (get-in id [:contract-id :chain-id])
|
||||
address (get-in id [:contract-id :address])]
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content (fn []
|
||||
[options-drawer/view
|
||||
{:chain-id chain-id
|
||||
:address address
|
||||
:name (:name collectible-details)
|
||||
:image (:uri preview-url)}])}])))
|
||||
|
||||
(defn- on-collectible-press
|
||||
[{:keys [id]}]
|
||||
(rf/dispatch [:wallet/get-collectible-details id]))
|
||||
|
||||
(defn view
|
||||
[{:keys [selected-tab]}]
|
||||
(let [collectible-list (rf/sub [:wallet/all-collectibles-list-in-selected-networks])
|
||||
|
@ -18,20 +34,7 @@
|
|||
:assets [assets/view]
|
||||
:collectibles [collectibles/view
|
||||
{:collectibles collectible-list
|
||||
:on-collectible-long-press (fn [{:keys [preview-url collectible-details id]}]
|
||||
(let [chain-id (get-in id [:contract-id :chain-id])
|
||||
address (get-in id [:contract-id :address])]
|
||||
(rf/dispatch
|
||||
[:show-bottom-sheet
|
||||
{:content (fn []
|
||||
[options-drawer/view
|
||||
{:chain-id chain-id
|
||||
:address address
|
||||
:name (:name
|
||||
collectible-details)
|
||||
:image (:uri
|
||||
preview-url)}])}])))
|
||||
:on-collectible-long-press on-collectible-long-press
|
||||
:on-end-reached request-collectibles
|
||||
:on-collectible-press (fn [{:keys [id]}]
|
||||
(rf/dispatch [:wallet/get-collectible-details id]))}]
|
||||
:on-collectible-press on-collectible-press}]
|
||||
[activity/view {:activities []}])]))
|
||||
|
|
Loading…
Reference in New Issue