From 00db44ab0deabcb774241fb26eedecddf4bb09c1 Mon Sep 17 00:00:00 2001 From: Omar Basem Date: Thu, 23 Feb 2023 07:46:42 +0400 Subject: [PATCH] Zoomable Image (#15078) * feat: zoomable image --- src/react_native/gesture.cljs | 37 +- src/react_native/reanimated.cljs | 17 + src/status_im2/contexts/chat/events.cljs | 10 + .../contexts/chat/lightbox/style.cljs | 3 +- .../contexts/chat/lightbox/view.cljs | 117 ++++--- .../chat/lightbox/zoomable_image/view.cljs | 321 ++++++++++++++++++ src/status_im2/subs/root.cljs | 2 + 7 files changed, 430 insertions(+), 77 deletions(-) create mode 100644 src/status_im2/contexts/chat/lightbox/zoomable_image/view.cljs diff --git a/src/react_native/gesture.cljs b/src/react_native/gesture.cljs index 4d08fe94ff..b1669b0f72 100644 --- a/src/react_native/gesture.cljs +++ b/src/react_native/gesture.cljs @@ -1,27 +1,34 @@ (ns react-native.gesture (:require ["react-native-gesture-handler" :refer - (GestureDetector Gesture gestureHandlerRootHOC TapGestureHandler State)] - [reagent.core :as reagent] - [oops.core :refer [oget]])) + (GestureDetector Gesture gestureHandlerRootHOC)] + [reagent.core :as reagent])) (def gesture-detector (reagent/adapt-react-class GestureDetector)) + (def gesture-handler-root-hoc gestureHandlerRootHOC) +(defn gesture-tap [] (.Tap ^js Gesture)) + (defn gesture-pan [] (.Pan ^js Gesture)) -(defn on-update [^js pan handler] (.onUpdate pan handler)) +(defn gesture-pinch [] (.Pinch ^js Gesture)) -(defn on-start [^js pan handler] (.onStart pan handler)) +(defn on-begin [gesture handler] (.onBegin ^js gesture handler)) -(defn on-end [^js pan handler] (.onEnd pan handler)) +(defn on-start [gesture handler] (.onStart ^js gesture handler)) -(def tap-gesture-handler - (reagent/adapt-react-class TapGestureHandler)) +(defn on-update [gesture handler] (.onUpdate ^js gesture handler)) -(def states - {:began (oget State "BEGAN") - :active (oget State "ACTIVE") - :cancelled (oget State "CANCELLED") - :end (oget State "END") - :failed (oget State "FAILED") - :undetermined (oget State "UNDETERMINED")}) +(defn on-end [gesture handler] (.onEnd ^js gesture handler)) + +(defn number-of-taps [gesture count] (.numberOfTaps ^js gesture count)) + +(defn enabled [gesture enabled?] (.enabled ^js gesture enabled?)) + +(defn average-touches [gesture average-touches?] (.averageTouches ^js gesture average-touches?)) + +(defn simultaneous + ([g1 g2] (.Simultaneous ^js Gesture g1 g2)) + ([g1 g2 g3] (.Simultaneous ^js Gesture g1 g2 g3))) + +(defn exclusive [g1 g2] (.Exclusive ^js Gesture g1 g2)) diff --git a/src/react_native/reanimated.cljs b/src/react_native/reanimated.cljs index 4f8d6b750b..794bbebbf4 100644 --- a/src/react_native/reanimated.cljs +++ b/src/react_native/reanimated.cljs @@ -9,6 +9,7 @@ withDelay withSpring withRepeat + withDecay Easing Keyframe cancelAnimation @@ -48,6 +49,7 @@ (def with-timing withTiming) (def with-delay withDelay) (def with-spring withSpring) +(def with-decay withDecay) (def key-frame Keyframe) (def with-repeat withRepeat) (def cancel-animation cancelAnimation) @@ -137,3 +139,18 @@ (js-obj "mass" mass "damping" damping "stiffness" stiffness)))) + +(defn animate-shared-value-with-decay + [anim velocity clamp] + (set-shared-value anim + (with-decay (clj->js {:velocity velocity + :clamp clamp})))) + +(def in-out + (.-inOut Easing)) + +(defn with-timing-duration + [val duration] + (with-timing val + (clj->js {:duration duration + :easing (in-out (.-quad ^js Easing))}))) diff --git a/src/status_im2/contexts/chat/events.cljs b/src/status_im2/contexts/chat/events.cljs index e53f9c0e4a..d0fe791839 100644 --- a/src/status_im2/contexts/chat/events.cljs +++ b/src/status_im2/contexts/chat/events.cljs @@ -335,3 +335,13 @@ {:events [:chat.ui/update-shared-element-id]} [{:keys [db]} shared-element-id] {:db (assoc db :shared-element-id shared-element-id)}) + +(rf/defn exit-lightbox-signal + {:events [:chat.ui/exit-lightbox-signal]} + [{:keys [db]} value] + {:db (assoc db :lightbox/exit-signal value)}) + +(rf/defn zoom-out-signal + {:events [:chat.ui/zoom-out-signal]} + [{:keys [db]} value] + {:db (assoc db :lightbox/zoom-out-signal value)}) diff --git a/src/status_im2/contexts/chat/lightbox/style.cljs b/src/status_im2/contexts/chat/lightbox/style.cljs index 4c2003697e..d2f388277c 100644 --- a/src/status_im2/contexts/chat/lightbox/style.cljs +++ b/src/status_im2/contexts/chat/lightbox/style.cljs @@ -1,6 +1,5 @@ (ns status-im2.contexts.chat.lightbox.style (:require [quo2.foundations.colors :as colors] - [react-native.platform :as platform] [react-native.reanimated :as reanimated])) (def container-view @@ -13,7 +12,7 @@ {:opacity opacity} {:position :absolute :left 20 - :top (if platform/ios? (+ 12 top-inset) 12) + :top (+ 12 top-inset) :z-index 4 :flex-direction :row :width "100%"})) diff --git a/src/status_im2/contexts/chat/lightbox/view.cljs b/src/status_im2/contexts/chat/lightbox/view.cljs index 1eb4cea078..26b5ffb9b7 100644 --- a/src/status_im2/contexts/chat/lightbox/view.cljs +++ b/src/status_im2/contexts/chat/lightbox/view.cljs @@ -3,14 +3,14 @@ [quo2.core :as quo] [quo2.foundations.colors :as colors] [react-native.core :as rn] - [react-native.fast-image :as fast-image] + [react-native.platform :as platform] [react-native.reanimated :as reanimated] [utils.re-frame :as rf] [react-native.safe-area :as safe-area] [reagent.core :as reagent] [status-im2.contexts.chat.lightbox.style :as style] [utils.datetime :as datetime] - [react-native.gesture :as gesture] + [status-im2.contexts.chat.lightbox.zoomable-image.view :as zoomable-image] [oops.core :refer [oget]])) (def flat-list-ref (atom nil)) @@ -19,35 +19,24 @@ (def focused-image-size 56) (defn toggle-opacity - [opacity-value border-value transparent?] + [opacity-value border-value transparent? index] (let [opacity (reanimated/get-shared-value opacity-value)] - (reanimated/set-shared-value opacity-value (reanimated/with-timing (if (= opacity 1) 0 1))) - (reanimated/set-shared-value border-value (reanimated/with-timing (if (= opacity 1) 0 12))) - (reset! transparent? (not @transparent?)))) + (if (= opacity 1) + (do + (reanimated/set-shared-value opacity-value (reanimated/with-timing 0)) + (js/setTimeout #(reset! transparent? (not @transparent?)) 400)) + (do + (reset! transparent? (not @transparent?)) + (js/setTimeout #(reanimated/set-shared-value opacity-value (reanimated/with-timing 1)) 50) + (js/setTimeout #(.scrollToIndex ^js @small-list-ref #js {:animated false :index index}) 100))) + (reanimated/set-shared-value border-value (reanimated/with-timing (if (= opacity 1) 0 12))))) (defn image - [message opacity-value border-value transparent?] - [:f> - (fn [] - (let [shared-element-id (rf/sub [:shared-element-id]) - width (:width (rn/get-window)) - height (* (or (:image-height message) 1000) - (/ width (or (:image-width message) 1000)))] - [gesture/tap-gesture-handler - {:on-handler-state-change (fn [e] - (when (= (oget e "nativeEvent.state") (:active gesture/states)) - (toggle-opacity opacity-value border-value transparent?)))} - [rn/view {:style {:flex-direction :row}} - [reanimated/view - {:style (reanimated/apply-animations-to-style - {:border-radius border-value} - {:overflow :hidden})} - [fast-image/fast-image - {:source {:uri (:image (:content message))} - :style {:width width - :height height} - :native-ID (when (= shared-element-id (:message-id message)) :shared-element)}]] - [rn/view {:style {:width 16}}]]]))]) + [message index _ {:keys [opacity-value border-value transparent?]}] + [rn/view {:style {:flex-direction :row}} + [zoomable-image/zoomable-image message index border-value + #(toggle-opacity opacity-value border-value transparent? index)] + [rn/view {:style {:width 16}}]]) (defn get-item-layout @@ -64,18 +53,20 @@ (let [changed (-> e (oget :changed) first) index (oget changed :index)] (reset! scroll-index index) - (.scrollToIndex ^js @small-list-ref #js {:animated true :index index}) + (when @small-list-ref (.scrollToIndex ^js @small-list-ref #js {:animated true :index index})) (rf/dispatch [:chat.ui/update-shared-element-id (:message-id (oget changed :item))]))) (defn top-view - [{:keys [from timestamp]} insets opacity-value transparent?] + [{:keys [from timestamp]} insets opacity-value index] [:f> (fn [] (let [display-name (first (rf/sub [:contacts/contact-two-names-by-identity from]))] [reanimated/view {:style (style/top-view-container (:top insets) opacity-value)} [rn/touchable-opacity - {:on-press #(when-not @transparent? (rf/dispatch [:navigate-back])) + {:on-press #(rf/dispatch (if platform/ios? + [:chat.ui/exit-lightbox-signal @index] + [:navigate-back])) :style style/close-container} [quo/icon :close {:size 20 :color colors/white}]] [rn/view {:style {:margin-left 12}} @@ -99,7 +90,6 @@ :style style/close-container} [quo/icon :options {:size 20 :color colors/white}]]]]))]) - (defn small-image [item index scroll-index] [:f> @@ -110,9 +100,13 @@ [rn/touchable-opacity {:active-opacity 1 :on-press (fn [] - (reset! scroll-index index) - (.scrollToIndex ^js @small-list-ref #js {:animated true :index index}) - (.scrollToIndex ^js @flat-list-ref #js {:animated true :index index}))} + (rf/dispatch [:chat.ui/zoom-out-signal @scroll-index]) + (js/setTimeout + (fn [] + (reset! scroll-index index) + (.scrollToIndex ^js @small-list-ref #js {:animated true :index index}) + (.scrollToIndex ^js @flat-list-ref #js {:animated true :index index})) + (if platform/ios? 50 150)))} [reanimated/fast-image {:source {:uri (:image (:content item))} :style (reanimated/apply-animations-to-style {:width size-value @@ -161,31 +155,34 @@ transparent? (reagent/atom false) opacity-value (reanimated/use-shared-value 1) border-value (reanimated/use-shared-value 12) - window-width (:width (rn/get-window))] + window-width (:width (rn/get-window)) + callback (fn [e] + (on-viewable-items-changed e + scroll-index))] (reset! data messages) + (rn/use-effect-once (fn [] + (.scrollToIndex ^js @flat-list-ref #js {:animated false :index index}) + js/undefined)) [safe-area/consumer (fn [insets] - [:f> - (fn [] - ;; We use setTimeout to enqueue `scrollToIndex` until the `data` has been updated. - (js/setTimeout #(.scrollToIndex ^js @flat-list-ref #js {:animated false :index index}) 0) - [rn/view {:style style/container-view} - [top-view (first messages) insets opacity-value transparent?] - [rn/flat-list - {:ref #(reset! flat-list-ref %) - :key-fn :message-id - :style {:width (+ window-width 16)} - :data @data - :render-fn (fn [item] [image item opacity-value border-value - transparent?]) - :horizontal true - :paging-enabled true - :get-item-layout get-item-layout - :viewability-config {:view-area-coverage-percent-threshold 50} - :on-viewable-items-changed (rn/use-callback (fn [e] - (on-viewable-items-changed e - scroll-index))) - :content-container-style {:justify-content :center - :align-items :center}}] - [bottom-view messages index scroll-index insets opacity-value]])])]))]) - + [rn/view {:style style/container-view} + (when-not @transparent? + [top-view (first messages) insets opacity-value scroll-index]) + [rn/flat-list + {:ref #(reset! flat-list-ref %) + :key-fn :message-id + :style {:width (+ window-width 16)} + :data @data + :render-fn image + :render-data {:opacity-value opacity-value + :border-value border-value + :transparent? transparent?} + :horizontal true + :paging-enabled true + :get-item-layout get-item-layout + :viewability-config {:view-area-coverage-percent-threshold 50} + :on-viewable-items-changed callback + :content-container-style {:justify-content :center + :align-items :center}}] + (when-not @transparent? + [bottom-view messages index scroll-index insets opacity-value])])]))]) diff --git a/src/status_im2/contexts/chat/lightbox/zoomable_image/view.cljs b/src/status_im2/contexts/chat/lightbox/zoomable_image/view.cljs new file mode 100644 index 0000000000..56eecc9c1f --- /dev/null +++ b/src/status_im2/contexts/chat/lightbox/zoomable_image/view.cljs @@ -0,0 +1,321 @@ +(ns status-im2.contexts.chat.lightbox.zoomable-image.view + (:require + [react-native.core :as rn] + [react-native.gesture :as gesture] + [react-native.platform :as platform] + [react-native.reanimated :as reanimated] + [reagent.core :as reagent] + [utils.re-frame :as rf] + [oops.core :refer [oget]])) + +;;;; Definitions +(def min-scale 1) + +(def double-tap-scale 2) + +(def max-scale 5) + +(def init-offset 0) + +(def velocity-factor 0.5) + +(def default-duration 300) + +;;;; Some aliases for reanimated methods, as they are used 10s of times in this file +(defn get-val + [animation] + (reanimated/get-shared-value animation)) + +(defn set-val + [animation value] + (reanimated/set-shared-value animation value)) + +(defn use-val + [value] + (reanimated/use-shared-value value)) + +(defn timing + ([value] + (timing value default-duration)) + ([value duration] + (reanimated/with-timing-duration value duration))) + +(defn set-val-decay + [animation velocity bounds] + (reanimated/animate-shared-value-with-decay animation (* velocity velocity-factor) bounds)) + +;;;; MATH +(defn get-max-offset + [size screen-size scale] + (/ (- (* size (min scale max-scale)) + screen-size) + 2)) + +(defn get-scale-diff + [new-scale saved-scale] + (- (dec new-scale) + (dec saved-scale))) + +(defn get-double-tap-offset + [size screen-size focal] + (let [center (/ size 2) + target-point (* (- center focal) double-tap-scale) + max-offset (get-max-offset size screen-size double-tap-scale) + translate-val (min (Math/abs target-point) max-offset)] + (if (neg? target-point) (- translate-val) translate-val))) + +(defn get-pinch-position + [scale-diff size focal] + (* (- (/ size 2) focal) scale-diff)) + + +;;;; 5 Gestures: tap, double-tap, pinch, pan-x, pan-y +(defn tap-gesture + [on-tap] + (-> + (gesture/gesture-tap) + (gesture/on-start #(on-tap)))) + +(defn double-tap-gesture + [{:keys [width height screen-height]} + {:keys [scale pan-x pan-x-start pan-y pan-y-start]} + {:keys [y-threshold-scale x-threshold-scale]} + rescale] + (-> + (gesture/gesture-tap) + (gesture/number-of-taps 2) + (gesture/on-start (fn [e] + (if (= (get-val scale) min-scale) + (let [translate-x (get-double-tap-offset width width (oget e "x")) + translate-y (get-double-tap-offset height screen-height (oget e "y"))] + (when (> double-tap-scale x-threshold-scale) + (set-val pan-x (timing translate-x)) + (set-val pan-x-start translate-x)) + (when (> double-tap-scale y-threshold-scale) + (set-val pan-y (timing translate-y)) + (set-val pan-y-start translate-y)) + (rescale double-tap-scale)) + (rescale min-scale)))))) + +(defn pinch-gesture + [{:keys [width height]} + {:keys [saved-scale scale pinch-x pinch-y pinch-x-start pinch-y-start pinch-x-max pinch-y-max]} + {:keys [pan-x-enabled? pan-y-enabled? x-threshold-scale y-threshold-scale focal-x focal-y]} + rescale] + (-> + (gesture/gesture-pinch) + (gesture/on-begin (fn [e] + (when platform/ios? + (reset! focal-x (oget e "focalX")) + (reset! focal-y (oget e "focalY"))))) + (gesture/on-start (fn [e] + (when platform/android? + (reset! focal-x (oget e "focalX")) + (reset! focal-y (oget e "focalY"))))) + (gesture/on-update (fn [e] + (let [new-scale (* (oget e "scale") (get-val saved-scale)) + scale-diff (get-scale-diff new-scale (get-val saved-scale)) + new-pinch-x (get-pinch-position scale-diff width @focal-x) + new-pinch-y (get-pinch-position scale-diff height @focal-y)] + (when (and (>= new-scale max-scale) (= (get-val pinch-x-max) js/Infinity)) + (set-val pinch-x-max (get-val pinch-x)) + (set-val pinch-y-max (get-val pinch-y))) + (set-val pinch-x (+ new-pinch-x (get-val pinch-x-start))) + (set-val pinch-y (+ new-pinch-y (get-val pinch-y-start))) + (set-val scale new-scale)))) + (gesture/on-end + (fn [] + (cond + (< (get-val scale) min-scale) + (rescale min-scale) + (> (get-val scale) max-scale) + (do + (set-val pinch-x (timing (get-val pinch-x-max))) + (set-val pinch-x-start (get-val pinch-x-max)) + (set-val pinch-x-max js/Infinity) + (set-val pinch-y (timing (get-val pinch-y-max))) + (set-val pinch-y-start (get-val pinch-y-max)) + (set-val pinch-y-max js/Infinity) + (set-val scale (timing max-scale)) + (set-val saved-scale max-scale) + (reset! pan-x-enabled? (> (get-val scale) x-threshold-scale)) + (reset! pan-y-enabled? (> (get-val scale) y-threshold-scale))) + :else + (do + (set-val saved-scale (get-val scale)) + (set-val pinch-x-start (get-val pinch-x)) + (set-val pinch-y-start (get-val pinch-y)) + (reset! pan-x-enabled? (> (get-val scale) x-threshold-scale)) + (reset! pan-y-enabled? (> (get-val scale) y-threshold-scale)))))))) + +(defn pan-x-gesture + [{:keys [width]} + {:keys [scale pan-x-start pan-x pinch-x pinch-x-start]} + {:keys [pan-x-enabled? x-threshold-scale]} + rescale] + (-> + (gesture/gesture-pan) + (gesture/enabled @pan-x-enabled?) + (gesture/average-touches false) + (gesture/on-update (fn [e] + (set-val pan-x (+ (get-val pan-x-start) (oget e "translationX"))))) + (gesture/on-end + (fn [e] + (let [curr-offset (+ (get-val pan-x) (get-val pinch-x-start)) + max-offset (get-max-offset width width (get-val scale)) + max-offset (if (neg? curr-offset) (- max-offset) max-offset)] + (cond + (< (get-val scale) x-threshold-scale) + (rescale min-scale) + (> (Math/abs curr-offset) (Math/abs max-offset)) + (do + (set-val pan-x (timing max-offset)) + (set-val pan-x-start max-offset) + (set-val pinch-x (timing init-offset)) + (set-val pinch-x-start init-offset)) + :else + (let [lower-bound (- (- (Math/abs max-offset)) (get-val pinch-x-start)) + upper-bound (- (Math/abs max-offset) (get-val pinch-x-start))] + (set-val pan-x-start (get-val pan-x)) + (set-val-decay pan-x (oget e "velocityX") [lower-bound upper-bound]) + (set-val-decay pan-x-start (oget e "velocityX") [lower-bound upper-bound])))))))) + + +(defn pan-y-gesture + [{:keys [height screen-height]} + {:keys [scale pan-y-start pan-y pinch-y pinch-y-start]} + {:keys [pan-y-enabled? y-threshold-scale]} + rescale] + (-> + (gesture/gesture-pan) + (gesture/enabled @pan-y-enabled?) + (gesture/average-touches false) + (gesture/on-update (fn [e] + (set-val pan-y (+ (get-val pan-y-start) (oget e "translationY"))))) + (gesture/on-end + (fn [e] + (let [curr-offset (+ (get-val pan-y) (get-val pinch-y-start)) + max-offset (get-max-offset height screen-height (get-val scale)) + max-offset (if (neg? curr-offset) (- max-offset) max-offset)] + (cond + (< (get-val scale) y-threshold-scale) + (rescale min-scale) + (> (Math/abs curr-offset) (Math/abs max-offset)) + (do + (set-val pan-y (timing max-offset)) + (set-val pan-y-start max-offset) + (set-val pinch-y (timing init-offset)) + (set-val pinch-y-start init-offset)) + :else + (let [lower-bound (- (- (Math/abs max-offset)) (get-val pinch-y-start)) + upper-bound (- (Math/abs max-offset) (get-val pinch-y-start))] + (set-val pan-y-start (get-val pan-y)) + (set-val-decay pan-y (oget e "velocityY") [lower-bound upper-bound]) + (set-val-decay pan-y-start (oget e "velocityY") [lower-bound upper-bound])))))))) + +(defn reset-values + [exit? + {:keys [pan-x pan-x-start pan-y pan-y-start pinch-x pinch-y pinch-x-start pinch-y-start]} + {:keys [focal-x focal-y]}] + (let [duration (if exit? 100 default-duration)] + (set-val pan-x (timing init-offset duration)) + (set-val pinch-x (timing init-offset duration)) + (set-val pan-x-start init-offset) + (set-val pinch-x-start init-offset) + (set-val pan-y (timing init-offset duration)) + (set-val pinch-y (timing init-offset duration)) + (set-val pinch-y-start init-offset) + (set-val pan-y-start init-offset) + (reset! focal-x nil) + (reset! focal-y nil))) + +(defn rescale-image + [value + exit? + {:keys [scale saved-scale] :as animations} + {:keys [pan-x-enabled? pan-y-enabled? x-threshold-scale y-threshold-scale] :as props}] + (set-val scale (timing value (if exit? 100 300))) + (set-val saved-scale value) + (when (= value min-scale) + (reset-values exit? animations props)) + (reset! pan-x-enabled? (> value x-threshold-scale)) + (reset! pan-y-enabled? (> value y-threshold-scale))) + +;;; On ios, when attempting to navigate back while zoomed in, the shared-element transition +;;; animation doesn't execute properly, so we need to zoom out first +(defn handle-exit-lightbox-signal + [exit-lightbox-signal index scale rescale] + (when (= exit-lightbox-signal index) + (if (> scale min-scale) + (do + (rescale min-scale true) + (js/setTimeout #(rf/dispatch [:navigate-back]) 70)) + (rf/dispatch [:navigate-back])) + (js/setTimeout #(rf/dispatch [:chat.ui/exit-lightbox-signal nil]) 500))) + +(defn handle-zoom-out-signal + [zoom-out-signal index scale rescale] + (when (and (= zoom-out-signal index) (> scale min-scale)) + (rescale min-scale true))) + +;;;; Finally, the component +(defn zoomable-image + [{:keys [image-width image-height content message-id]} index border-value on-tap] + [:f> + (fn [] + (let [shared-element-id (rf/sub [:shared-element-id]) + exit-lightbox-signal (rf/sub [:lightbox/exit-signal]) + zoom-out-signal (rf/sub [:lightbox/zoom-out-signal]) + width (:width (rn/get-window)) + height (* image-height (/ (:width (rn/get-window)) image-width)) + screen-height (:height (rn/get-window)) + dimensions {:width width + :height height + :screen-height screen-height} + animations {:scale (use-val min-scale) + :saved-scale (use-val min-scale) + :pan-x-start (use-val init-offset) + :pan-x (use-val init-offset) + :pan-y-start (use-val init-offset) + :pan-y (use-val init-offset) + :pinch-x-start (use-val init-offset) + :pinch-x (use-val init-offset) + :pinch-y-start (use-val init-offset) + :pinch-y (use-val init-offset) + :pinch-x-max (use-val js/Infinity) + :pinch-y-max (use-val js/Infinity)} + props {:x-threshold-scale 1 + :y-threshold-scale (/ screen-height (min screen-height height)) + :pan-x-enabled? (reagent/atom false) + :pan-y-enabled? (reagent/atom false) + :focal-x (reagent/atom nil) + :focal-y (reagent/atom nil)} + rescale (fn [value exit?] + (rescale-image value exit? animations props))] + (handle-exit-lightbox-signal exit-lightbox-signal index (get-val (:scale animations)) rescale) + (handle-zoom-out-signal zoom-out-signal index (get-val (:scale animations)) rescale) + (println "ZZZ" zoom-out-signal) + [:f> + (fn [] + (let [tap (tap-gesture on-tap) + double-tap (double-tap-gesture dimensions animations props rescale) + pinch (pinch-gesture dimensions animations props rescale) + 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/fast-image + {:source {:uri (:image content)} + :native-ID (when (= shared-element-id message-id) :shared-element) + :style (reanimated/apply-animations-to-style + {:transform [{:translateX (:pan-x animations)} + {:translateY (:pan-y animations)} + {:translateX (:pinch-x animations)} + {:translateY (:pinch-y animations)} + {:scale (:scale animations)}] + :border-radius border-value} + {:width width + :height height})}]]))]))]) + diff --git a/src/status_im2/subs/root.cljs b/src/status_im2/subs/root.cljs index 56c90055c3..a7debd38cf 100644 --- a/src/status_im2/subs/root.cljs +++ b/src/status_im2/subs/root.cljs @@ -115,6 +115,8 @@ (reg-root-key-sub :chats/mention-suggestions :chats/mention-suggestions) (reg-root-key-sub :chat/inputs-with-mentions :chat/inputs-with-mentions) (reg-root-key-sub :chats-home-list :chats-home-list) +(reg-root-key-sub :lightbox/exit-signal :lightbox/exit-signal) +(reg-root-key-sub :lightbox/zoom-out-signal :lightbox/zoom-out-signal) ;;messages (reg-root-key-sub :messages/messages :messages)