refactor: zoomable component (#16022)

refactor: zoomable comp
This commit is contained in:
Omar Basem 2023-05-31 10:01:35 +04:00 committed by GitHub
parent e8b956d4f4
commit 2b701f9af0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 102 deletions

View File

@ -4,10 +4,8 @@
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.gesture :as gesture] [react-native.gesture :as gesture]
[react-native.navigation :as navigation]
[react-native.orientation :as orientation] [react-native.orientation :as orientation]
[react-native.platform :as platform] [react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area] [react-native.safe-area :as safe-area]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im2.contexts.chat.lightbox.animations :as anim] [status-im2.contexts.chat.lightbox.animations :as anim]
@ -83,27 +81,6 @@
(when (and enabled? (not= result orientation/landscape-right)) (when (and enabled? (not= result orientation/landscape-right))
(handle-orientation result props state animations)))))))) (handle-orientation result props state animations))))))))
(defn toggle-opacity
[index {:keys [opacity-value border-value transparent? props]} portrait?]
(let [{:keys [small-list-ref]} props
opacity (reanimated/get-shared-value opacity-value)]
(if (= opacity 1)
(do
(when platform/ios?
;; status-bar issue: https://github.com/status-im/status-mobile/issues/15343
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible false}}) 75))
(anim/animate opacity-value 0)
(js/setTimeout #(reset! transparent? (not @transparent?)) 400))
(do
(reset! transparent? (not @transparent?))
(js/setTimeout #(anim/animate opacity-value 1) 50)
(js/setTimeout #(when @small-list-ref
(.scrollToIndex ^js @small-list-ref #js {:animated false :index index}))
100)
(when (and platform/ios? portrait?)
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible true}}) 150))))
(anim/animate border-value (if (= opacity 1) 0 12))))
(defn drag-gesture (defn drag-gesture
[{:keys [pan-x pan-y background-color opacity layout]} x? set-full-height?] [{:keys [pan-x pan-y background-color opacity layout]} x? set-full-height?]
(-> (->

View File

@ -38,8 +38,7 @@
[message index _ {:keys [screen-width screen-height] :as args}] [message index _ {:keys [screen-width screen-height] :as args}]
[rn/view [rn/view
{:style (style/image (+ screen-width constants/separator-width) screen-height)} {:style (style/image (+ screen-width constants/separator-width) screen-height)}
[zoomable-image/zoomable-image message index args [:f> zoomable-image/zoomable-image message index args]
#(utils/toggle-opacity index args %)]
[rn/view {:style {:width constants/separator-width}}]]) [rn/view {:style {:width constants/separator-width}}]])
(defn lightbox-content (defn lightbox-content

View File

@ -1,8 +1,11 @@
(ns status-im2.contexts.chat.lightbox.zoomable-image.utils (ns status-im2.contexts.chat.lightbox.zoomable-image.utils
(:require (:require
[clojure.string :as string] [clojure.string :as string]
[react-native.navigation :as navigation]
[react-native.orientation :as orientation] [react-native.orientation :as orientation]
[react-native.platform :as platform] [react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[status-im2.contexts.chat.lightbox.zoomable-image.constants :as c] [status-im2.contexts.chat.lightbox.zoomable-image.constants :as c]
[status-im2.contexts.chat.lightbox.animations :as anim] [status-im2.contexts.chat.lightbox.animations :as anim]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
@ -36,9 +39,9 @@
exit? exit?
{:keys [x-threshold-scale y-threshold-scale]} {:keys [x-threshold-scale y-threshold-scale]}
{:keys [scale saved-scale] :as animations} {:keys [scale saved-scale] :as animations}
{:keys [pan-x-enabled? pan-y-enabled?] :as props}] {:keys [pan-x-enabled? pan-y-enabled?] :as state}]
(when (= value c/min-scale) (when (= value c/min-scale)
(reset-values exit? animations props)) (reset-values exit? animations state))
(anim/animate scale value (if exit? 100 c/default-duration)) (anim/animate scale value (if exit? 100 c/default-duration))
(anim/set-val saved-scale value) (anim/set-val saved-scale value)
(reset! pan-x-enabled? (> value x-threshold-scale)) (reset! pan-x-enabled? (> value x-threshold-scale))
@ -102,6 +105,27 @@
(when (and (= zoom-out-signal index) (> scale c/min-scale)) (when (and (= zoom-out-signal index) (> scale c/min-scale))
(rescale c/min-scale true))) (rescale c/min-scale true)))
(defn toggle-opacity
[index {:keys [opacity-value border-value transparent? props]} portrait?]
(let [{:keys [small-list-ref]} props
opacity (reanimated/get-shared-value opacity-value)]
(if (= opacity 1)
(do
(when platform/ios?
;; status-bar issue: https://github.com/status-im/status-mobile/issues/15343
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible false}}) 75))
(anim/animate opacity-value 0)
(js/setTimeout #(reset! transparent? (not @transparent?)) 400))
(do
(reset! transparent? (not @transparent?))
(js/setTimeout #(anim/animate opacity-value 1) 50)
(js/setTimeout #(when @small-list-ref
(.scrollToIndex ^js @small-list-ref #js {:animated false :index index}))
100)
(when (and platform/ios? portrait?)
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible true}}) 150))))
(anim/animate border-value (if (= opacity 1) 0 12))))
;;; Dimensions ;;; Dimensions
(defn get-dimensions (defn get-dimensions
"Calculates all required dimensions. Dimensions calculations are different on iOS and Android because landscape "Calculates all required dimensions. Dimensions calculations are different on iOS and Android because landscape
@ -162,3 +186,28 @@
(if (or (> focal max) (< focal min)) (if (or (> focal max) (< focal min))
(/ screen-size 2) (/ screen-size 2)
focal))) focal)))
;;; INITIALIZATIONS
(defn init-animations
[]
{:scale (anim/use-val c/min-scale)
:saved-scale (anim/use-val c/min-scale)
:pan-x-start (anim/use-val c/init-offset)
:pan-x (anim/use-val c/init-offset)
:pan-y-start (anim/use-val c/init-offset)
:pan-y (anim/use-val c/init-offset)
:pinch-x-start (anim/use-val c/init-offset)
:pinch-x (anim/use-val c/init-offset)
:pinch-y-start (anim/use-val c/init-offset)
:pinch-y (anim/use-val c/init-offset)
:pinch-x-max (anim/use-val js/Infinity)
:pinch-y-max (anim/use-val js/Infinity)
:rotate (anim/use-val c/init-rotation)
:rotate-scale (anim/use-val c/min-scale)})
(defn init-state
[]
{:pan-x-enabled? (reagent/atom false)
:pan-y-enabled? (reagent/atom false)
:focal-x (reagent/atom nil)
:focal-y (reagent/atom nil)})

View File

@ -4,7 +4,6 @@
[react-native.gesture :as gesture] [react-native.gesture :as gesture]
[react-native.platform :as platform] [react-native.platform :as platform]
[react-native.reanimated :as reanimated] [react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[oops.core :refer [oget]] [oops.core :refer [oget]]
[react-native.orientation :as orientation] [react-native.orientation :as orientation]
@ -80,7 +79,7 @@
[{:keys [width height screen-height screen-width x-threshold-scale y-threshold-scale] :as dimensions} [{:keys [width height screen-height screen-width x-threshold-scale y-threshold-scale] :as dimensions}
{:keys [saved-scale scale pinch-x pinch-y pinch-x-start pinch-y-start pinch-x-max pinch-y-max] {:keys [saved-scale scale pinch-x pinch-y pinch-x-start pinch-y-start pinch-x-max pinch-y-max]
:as animations} :as animations}
{:keys [focal-x focal-y] :as props} {:keys [focal-x focal-y] :as state}
rescale rescale
transparent? transparent?
toggle-opacity] toggle-opacity]
@ -135,7 +134,7 @@
(utils/center-x animations false)) (utils/center-x animations false))
(when (< (anim/get-val scale) y-threshold-scale) (when (< (anim/get-val scale) y-threshold-scale)
(utils/center-y animations false)))) (utils/center-y animations false))))
(finalize-pinch dimensions animations props))))) (finalize-pinch dimensions animations state)))))
(defn pan-x-gesture (defn pan-x-gesture
[{:keys [width screen-width x-threshold-scale]} [{:keys [width screen-width x-threshold-scale]}
@ -204,76 +203,61 @@
(anim/animate-decay pan-y velocity [lower-bound upper-bound]) (anim/animate-decay pan-y velocity [lower-bound upper-bound])
(anim/animate-decay pan-y-start velocity [lower-bound upper-bound])))))))) (anim/animate-decay pan-y-start velocity [lower-bound upper-bound]))))))))
(defn- f-zoomable-image
[dimensions animations state rescale curr-orientation content focused? index render-data]
(let [{:keys [transparent? set-full-height?]} render-data
portrait? (= curr-orientation orientation/portrait)
on-tap #(utils/toggle-opacity index render-data portrait?)
tap (tap-gesture on-tap)
double-tap (double-tap-gesture dimensions animations rescale transparent? on-tap)
pinch (pinch-gesture dimensions animations state rescale transparent? on-tap)
pan-x (pan-x-gesture dimensions animations state rescale)
pan-y (pan-y-gesture dimensions animations state rescale)
composed-gestures (gesture/exclusive
(gesture/simultaneous pinch pan-x pan-y)
(gesture/exclusive double-tap tap))]
[gesture/gesture-detector {:gesture composed-gestures}
[reanimated/view
{:style (style/container dimensions
animations
@set-full-height?
(= curr-orientation orientation/portrait))}
[reanimated/fast-image
{:source {:uri (:image content)}
:native-ID (when focused? :shared-element)
:style (style/image dimensions animations (:border-value render-data))}]]]))
(defn zoomable-image (defn zoomable-image
[{:keys [image-width image-height content message-id]} index args on-tap] [{:keys [image-width image-height content message-id]} index render-data]
[:f> (let [state (utils/init-state)
(fn [] shared-element-id (rf/sub [:shared-element-id])
(let [{:keys [transparent? set-full-height?]} args exit-lightbox-signal (rf/sub [:lightbox/exit-signal])
shared-element-id (rf/sub [:shared-element-id]) zoom-out-signal (rf/sub [:lightbox/zoom-out-signal])
exit-lightbox-signal (rf/sub [:lightbox/exit-signal]) curr-orientation (or (rf/sub [:lightbox/orientation])
zoom-out-signal (rf/sub [:lightbox/zoom-out-signal]) orientation/portrait)
focused? (= shared-element-id message-id) {:keys [set-full-height?]} render-data
curr-orientation (or (rf/sub [:lightbox/orientation]) focused? (= shared-element-id message-id)
orientation/portrait) dimensions (utils/get-dimensions
portrait? (= curr-orientation orientation/portrait) (or image-width c/default-dimension)
dimensions (utils/get-dimensions (or image-height c/default-duration)
(or image-width c/default-dimension) curr-orientation
(or image-height c/default-duration) render-data)
curr-orientation animations (utils/init-animations)
args) rescale (fn [value exit?]
animations {:scale (anim/use-val c/min-scale) (utils/rescale-image value
:saved-scale (anim/use-val c/min-scale) exit?
:pan-x-start (anim/use-val c/init-offset) dimensions
:pan-x (anim/use-val c/init-offset) animations
:pan-y-start (anim/use-val c/init-offset) state))]
:pan-y (anim/use-val c/init-offset) (rn/use-effect (fn []
:pinch-x-start (anim/use-val c/init-offset) (js/setTimeout #(reset! set-full-height? true) 500)))
:pinch-x (anim/use-val c/init-offset) (when platform/ios?
:pinch-y-start (anim/use-val c/init-offset) (utils/handle-orientation-change curr-orientation focused? dimensions animations state)
:pinch-y (anim/use-val c/init-offset) (utils/handle-exit-lightbox-signal exit-lightbox-signal
:pinch-x-max (anim/use-val js/Infinity) index
:pinch-y-max (anim/use-val js/Infinity) (anim/get-val (:scale animations))
:rotate (anim/use-val c/init-rotation) rescale
:rotate-scale (anim/use-val c/min-scale)} set-full-height?))
props {:pan-x-enabled? (reagent/atom false) (utils/handle-zoom-out-signal zoom-out-signal index (anim/get-val (:scale animations)) rescale)
:pan-y-enabled? (reagent/atom false) [:f> f-zoomable-image dimensions animations state rescale curr-orientation content focused?
:focal-x (reagent/atom nil) index render-data]))
:focal-y (reagent/atom nil)}
rescale (fn [value exit?]
(utils/rescale-image value
exit?
dimensions
animations
props))]
(rn/use-effect (fn []
(js/setTimeout #(reset! set-full-height? true) 500)))
(when platform/ios?
(utils/handle-orientation-change curr-orientation focused? dimensions animations props)
(utils/handle-exit-lightbox-signal exit-lightbox-signal
index
(anim/get-val (:scale animations))
rescale
set-full-height?))
(utils/handle-zoom-out-signal zoom-out-signal index (anim/get-val (:scale animations)) rescale)
[:f>
(fn []
(let [tap (tap-gesture #(on-tap portrait?))
double-tap
(double-tap-gesture dimensions animations rescale transparent? #(on-tap portrait?))
pinch
(pinch-gesture dimensions animations props rescale transparent? #(on-tap portrait?))
pan-x (pan-x-gesture dimensions animations props rescale)
pan-y (pan-y-gesture dimensions animations props rescale)
composed-gestures (gesture/exclusive
(gesture/simultaneous pinch pan-x pan-y)
(gesture/exclusive double-tap tap))]
[gesture/gesture-detector {:gesture composed-gestures}
[reanimated/view
{:style (style/container dimensions
animations
@set-full-height?
(= curr-orientation orientation/portrait))}
[reanimated/fast-image
{:source {:uri (:image content)}
:native-ID (when focused? :shared-element)
:style (style/image dimensions animations (:border-value args))}]]]))]))])