diff --git a/src/quo/components/profile/collectible_list_item/view.cljs b/src/quo/components/profile/collectible_list_item/view.cljs index 5fcc274aa8..545908859e 100644 --- a/src/quo/components/profile/collectible_list_item/view.cljs +++ b/src/quo/components/profile/collectible_list_item/view.cljs @@ -34,6 +34,12 @@ (set-state (fn [prev-state] (assoc prev-state :image-loaded? true))))) +(defn on-load + [evt set-state] + (let [source (.. evt -nativeEvent -source) + aspect-ratio (/ (.-width source) (.-height source))] + (set-state (fn [prev-state] (assoc prev-state :image-aspect-ratio aspect-ratio))))) + (defn on-load-error [set-state] (js/setTimeout (fn [] @@ -147,6 +153,7 @@ [reanimated/view {:style (style/supported-file image-opacity)} [rn/image {:style style/image + :on-load #(on-load % set-state) :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 @@ -197,6 +204,7 @@ [reanimated/view {:style (style/supported-file image-opacity)} [rn/image {:style style/image + :on-load #(on-load % set-state) :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 @@ -219,12 +227,13 @@ (defn- view-internal [{:keys [container-style type on-press on-long-press supported-file?] :as props}] - (let [[state set-state] (rn/use-state {:image-loaded? false - :image-error? false - :avatar-loaded? false}) + (let [[state set-state] (rn/use-state {:image-loaded? false + :image-aspect-ratio nil + :image-error? false + :avatar-loaded? false}) collectible-ready? (or (:image-loaded? state) (not supported-file?))] [rn/pressable - {:on-press (when collectible-ready? on-press) + {:on-press (when collectible-ready? #(on-press (:image-aspect-ratio state))) :on-long-press (when collectible-ready? on-long-press) :accessibility-label :collectible-list-item :style container-style} diff --git a/src/quo/components/profile/expanded_collectible/style.cljs b/src/quo/components/profile/expanded_collectible/style.cljs index efb794fe5f..2a757ecbf5 100644 --- a/src/quo/components/profile/expanded_collectible/style.cljs +++ b/src/quo/components/profile/expanded_collectible/style.cljs @@ -1,10 +1,13 @@ (ns quo.components.profile.expanded-collectible.style (:require [quo.foundations.colors :as colors])) -(def container - {:align-items :center - :justify-content :center - :border-radius 16}) +(defn container + [aspect-ratio] + (cond-> {:align-items :center + :justify-content :center + :border-radius 16} + aspect-ratio (assoc :width "100%" + :aspect-ratio aspect-ratio))) (defn image [square? aspect-ratio theme] @@ -25,18 +28,43 @@ :border-color (colors/theme-colors colors/neutral-80-opa-5 colors/white-opa-5 theme)}) (defn fallback - [{:keys [theme]}] - {: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 - :border-radius 16 - :width "100%" - :aspect-ratio 1 - :align-items :center - :justify-content :center}) + ([theme] + (fallback theme 1)) + ([theme aspect-ratio] + {: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 + :border-radius 16 + :width "100%" + :aspect-ratio aspect-ratio + :align-items :center + :justify-content :center})) (def counter {:position :absolute :top 12 :right 12}) + +(defn loading-image-with-opacity + [opacity] + [{:align-items :center + :aspect-ratio 1 + :border-radius 16 + :justify-content :center + :opacity opacity + :position :absolute + :width "100%" + :z-index 1}]) + +(defn gradient-view + [aspect-ratio] + {:border-radius 16 + :width "100%" + :aspect-ratio aspect-ratio + :align-items :center + :justify-content :center}) + +(defn supported-file + [opacity] + {:opacity opacity}) diff --git a/src/quo/components/profile/expanded_collectible/view.cljs b/src/quo/components/profile/expanded_collectible/view.cljs index 40e12805b5..17e298a43d 100644 --- a/src/quo/components/profile/expanded_collectible/view.cljs +++ b/src/quo/components/profile/expanded_collectible/view.cljs @@ -1,17 +1,53 @@ (ns quo.components.profile.expanded-collectible.view (:require [clojure.string :as string] - [promesa.core :as promesa] [quo.components.counter.collectible-counter.view :as collectible-counter] [quo.components.icon :as icon] [quo.components.markdown.text :as text] [quo.components.profile.expanded-collectible.style :as style] [quo.foundations.colors :as colors] + [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- loading-image + [{:keys [theme gradient-color-index loader-opacity aspect-ratio]}] + [reanimated/view + {:style (style/loading-image-with-opacity loader-opacity)} + [gradients/view + {:theme theme + :container-style (style/gradient-view aspect-ratio) + :color-index gradient-color-index}]]) + (defn- counter-view [counter] [collectible-counter/view @@ -21,7 +57,7 @@ (defn- fallback-view [{:keys [label theme counter on-mount]}] (rn/use-mount on-mount) - [rn/view {:style (style/fallback {:theme theme})} + [rn/view {:style (style/fallback theme)} [counter-view counter] [rn/view [icon/icon :i/sad {:color (colors/theme-colors colors/neutral-40 colors/neutral-50 theme)}]] @@ -33,45 +69,57 @@ (defn view-internal [{:keys [container-style square? on-press counter image-src native-ID supported-file? - on-collectible-load]}] - (let [theme (quo.theme/use-theme) - [image-size set-image-size] (rn/use-state {}) - [image-error? set-image-error] (rn/use-state (or (nil? image-src) - (string/blank? image-src)))] - (rn/use-effect - (fn [] - (promesa/let [[image-width image-height] (rn/image-get-size image-src)] - (set-image-size {:width image-width - :height image-height - :aspect-ratio (/ image-width image-height)}))) - [image-src]) + on-collectible-load aspect-ratio]}] + (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)) + [state set-state] (rn/use-state {:image-loaded? false + :image-error? (or (nil? image-src) + (string/blank? + image-src))})] [rn/pressable - {:on-press (when (and (not image-error?) supported-file?) on-press) + {:on-press (when (and (not (:image-error? state)) supported-file?) on-press) :accessibility-label :expanded-collectible - :style (merge container-style style/container)} + :style (merge container-style + (style/container aspect-ratio))} (cond (not supported-file?) [fallback-view - {:label (i18n/label :t/unsupported-file) - :counter counter - :theme theme - :on-mount on-collectible-load}] + {:aspect-ratio aspect-ratio + :label (i18n/label :t/unsupported-file) + :counter counter + :theme theme + :on-mount on-collectible-load}] - image-error? + (:image-error? state) [fallback-view {:label (i18n/label :t/cant-fetch-info) :counter counter :theme theme :on-mount on-collectible-load}] - :else - [rn/view + (not (:image-loaded? state)) + [loading-image + {:aspect-ratio aspect-ratio + :loader-opacity loader-opacity + :theme theme + :gradient-color-index :gradient-5}]) + (when supported-file? + [reanimated/view {:style (style/supported-file image-opacity)} [rn/image - {:style (style/image square? (:aspect-ratio image-size) theme) - :source image-src - :native-ID native-ID - :on-error #(set-image-error true) - :on-load on-collectible-load}] + {:style (style/image square? aspect-ratio theme) + :source image-src + :native-ID native-ID + :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) + :on-load on-collectible-load}] (when counter [counter-view counter]) [rn/view {:style (style/collectible-border theme)}]])])) @@ -81,6 +129,7 @@ [:catn [:props [:map {:closed true} + [:aspect-ratio {:optional true} [:maybe number?]] [:image-src {:optional true} [:maybe string?]] [:supported-file? {:optional true} [:maybe boolean?]] [:container-style {:optional true} [:maybe :map]] diff --git a/src/status_im/contexts/wallet/account/tabs/view.cljs b/src/status_im/contexts/wallet/account/tabs/view.cljs index 9a9a2179b3..9eee1e388a 100644 --- a/src/status_im/contexts/wallet/account/tabs/view.cljs +++ b/src/status_im/contexts/wallet/account/tabs/view.cljs @@ -12,8 +12,8 @@ [utils.re-frame :as rf])) (defn- on-collectible-press - [{:keys [id]}] - (rf/dispatch [:wallet/get-collectible-details id])) + [{:keys [id]} aspect-ratio] + (rf/dispatch [:wallet/get-collectible-details id aspect-ratio])) (defn- on-collectible-long-press [{:keys [preview-url collectible-details id]}] diff --git a/src/status_im/contexts/wallet/collectible/events.cljs b/src/status_im/contexts/wallet/collectible/events.cljs index 96e31d99f3..f41540edc7 100644 --- a/src/status_im/contexts/wallet/collectible/events.cljs +++ b/src/status_im/contexts/wallet/collectible/events.cljs @@ -167,12 +167,13 @@ (rf/reg-event-fx :wallet/get-collectible-details - (fn [_ [collectible-id]] + (fn [{:keys [db]} [collectible-id aspect-ratio]] (let [request-id 0 collectible-id-converted (cske/transform-keys transforms/->PascalCaseKeyword collectible-id) data-type (collectible-data-types :details) request-params [request-id [collectible-id-converted] data-type]] - {:fx [[:json-rpc/call + {:db (assoc-in db [:wallet :last-collectible-aspect-ratio] aspect-ratio) + :fx [[:json-rpc/call [{:method "wallet_getCollectiblesByUniqueIDAsync" :params request-params :on-error (fn [error] @@ -197,7 +198,7 @@ (rf/reg-event-fx :wallet/clear-last-collectible-details (fn [{:keys [db]}] - {:db (update-in db [:wallet] dissoc :last-collectible-details)})) + {:db (update-in db [:wallet] dissoc :last-collectible-details :last-collectible-aspect-ratio)})) (rf/reg-event-fx :wallet/trigger-share-collectible (fn [_ [{:keys [title uri]}]] diff --git a/src/status_im/contexts/wallet/collectible/style.cljs b/src/status_im/contexts/wallet/collectible/style.cljs index c590db43c5..00292cf576 100644 --- a/src/status_im/contexts/wallet/collectible/style.cljs +++ b/src/status_im/contexts/wallet/collectible/style.cljs @@ -12,8 +12,8 @@ (defn preview-container [] - {:margin-horizontal 8 - :margin-top (+ (header-height) 12)}) + {:padding-horizontal 8 + :margin-top (+ (header-height) 12)}) (def header {:margin-horizontal 20 diff --git a/src/status_im/contexts/wallet/collectible/view.cljs b/src/status_im/contexts/wallet/collectible/view.cljs index 7b456d1c43..7682e2d7d9 100644 --- a/src/status_im/contexts/wallet/collectible/view.cljs +++ b/src/status_im/contexts/wallet/collectible/view.cljs @@ -150,6 +150,7 @@ set-title-ref (rn/use-callback #(reset! title-ref %)) animation-shared-element-id (rf/sub [:animation-shared-element-id]) collectible-owner (rf/sub [:wallet/last-collectible-details-owner]) + aspect-ratio (rf/sub [:wallet/last-collectible-aspect-ratio]) {:keys [id preview-url collection-data @@ -180,7 +181,8 @@ [rn/view [gradient-layer preview-uri] [quo/expanded-collectible - {:image-src preview-uri + {:aspect-ratio aspect-ratio + :image-src preview-uri :container-style (style/preview-container) :counter (utils/collectible-owned-counter total-owned) :native-ID (when (= animation-shared-element-id token-id) :shared-element) diff --git a/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs b/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs index 9d09344884..e0693ffd1c 100644 --- a/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs +++ b/src/status_im/contexts/wallet/common/collectibles_tab/view.cljs @@ -14,7 +14,7 @@ :as collectible} index] (let [on-press-fn (rn/use-callback #(when on-press - (on-press collectible))) + (on-press collectible %))) on-long-press-fn (rn/use-callback #(when on-long-press (on-long-press collectible)))] [quo/collectible-list-item diff --git a/src/status_im/contexts/wallet/home/tabs/view.cljs b/src/status_im/contexts/wallet/home/tabs/view.cljs index 7ef4dcffe6..4de089ae3a 100644 --- a/src/status_im/contexts/wallet/home/tabs/view.cljs +++ b/src/status_im/contexts/wallet/home/tabs/view.cljs @@ -18,8 +18,8 @@ :image (:uri preview-url)}])}])) (defn- on-collectible-press - [{:keys [id]}] - (rf/dispatch [:wallet/get-collectible-details id])) + [{:keys [id]} aspect-ratio] + (rf/dispatch [:wallet/get-collectible-details id aspect-ratio])) (defn view [{:keys [selected-tab]}] diff --git a/src/status_im/subs/wallet/collectibles.cljs b/src/status_im/subs/wallet/collectibles.cljs index d04ddfb4f9..fdcf56b7fc 100644 --- a/src/status_im/subs/wallet/collectibles.cljs +++ b/src/status_im/subs/wallet/collectibles.cljs @@ -97,6 +97,12 @@ (let [last-collectible (:last-collectible-details wallet)] (assoc last-collectible :preview-url (preview-url last-collectible))))) +(re-frame/reg-sub + :wallet/last-collectible-aspect-ratio + :<- [:wallet] + (fn [wallet] + (:last-collectible-aspect-ratio wallet))) + (re-frame/reg-sub :wallet/last-collectible-details-chain-id :<- [:wallet/last-collectible-details]