From 7392b9bf50f932abc1397cc9662390cde6b70f26 Mon Sep 17 00:00:00 2001 From: Omar Basem Date: Tue, 11 Jul 2023 13:41:34 +0400 Subject: [PATCH] Lightbox opacity swipe animation, and margins (#16510) * feat: lightbox animations --- .../contexts/chat/lightbox/bottom_view.cljs | 2 +- .../chat/lightbox/text_sheet/utils.cljs | 13 +-- .../chat/lightbox/text_sheet/view.cljs | 13 ++- .../contexts/chat/lightbox/utils.cljs | 67 +++++++++---- .../contexts/chat/lightbox/view.cljs | 34 ++++--- .../lightbox/zoomable_image/constants.cljs | 2 + .../chat/lightbox/zoomable_image/style.cljs | 10 +- .../chat/lightbox/zoomable_image/utils.cljs | 98 ++++++++++--------- .../chat/lightbox/zoomable_image/view.cljs | 3 +- 9 files changed, 146 insertions(+), 96 deletions(-) diff --git a/src/status_im2/contexts/chat/lightbox/bottom_view.cljs b/src/status_im2/contexts/chat/lightbox/bottom_view.cljs index e2a0dc1499..78e93bc3bc 100644 --- a/src/status_im2/contexts/chat/lightbox/bottom_view.cljs +++ b/src/status_im2/contexts/chat/lightbox/bottom_view.cljs @@ -58,7 +58,7 @@ :start {:x 0 :y 1} :end {:x 0 :y 0} :style (style/gradient-container insets animations derived)} - [text-sheet/view messages animations state] + [text-sheet/view messages animations state props] [rn/flat-list {:ref #(reset! (:small-list-ref props) %) :key-fn :message-id diff --git a/src/status_im2/contexts/chat/lightbox/text_sheet/utils.cljs b/src/status_im2/contexts/chat/lightbox/text_sheet/utils.cljs index a0eeb8149c..d7a7647768 100644 --- a/src/status_im2/contexts/chat/lightbox/text_sheet/utils.cljs +++ b/src/status_im2/contexts/chat/lightbox/text_sheet/utils.cljs @@ -40,12 +40,13 @@ (defn expand-sheet [{:keys [derived-value overlay-opacity saved-top]} - expanded-height max-height overlay-z-index expanded?] - (reanimated/animate derived-value expanded-height) - (reanimated/animate overlay-opacity (/ expanded-height max-height)) - (reanimated/set-shared-value saved-top (- expanded-height)) - (reset! overlay-z-index 1) - (reset! expanded? true)) + expanded-height max-height overlay-z-index expanded? text-sheet-lock?] + (when-not @text-sheet-lock? + (reanimated/animate derived-value expanded-height) + (reanimated/animate overlay-opacity (/ expanded-height max-height)) + (reanimated/set-shared-value saved-top (- expanded-height)) + (reset! overlay-z-index 1) + (reset! expanded? true))) (defn on-scroll [e expanded? dragging? {:keys [gradient-opacity]}] diff --git a/src/status_im2/contexts/chat/lightbox/text_sheet/view.cljs b/src/status_im2/contexts/chat/lightbox/text_sheet/view.cljs index 2471fbc194..60dcb4aa19 100644 --- a/src/status_im2/contexts/chat/lightbox/text_sheet/view.cljs +++ b/src/status_im2/contexts/chat/lightbox/text_sheet/view.cljs @@ -20,7 +20,7 @@ [rn/view {:style style/bar}]])) (defn text-sheet - [messages overlay-opacity overlay-z-index] + [messages overlay-opacity overlay-z-index text-sheet-lock?] (let [text-height (reagent/atom 0) expanded? (reagent/atom false) dragging? (atom false)] @@ -49,7 +49,12 @@ [reanimated/touchable-opacity {:active-opacity 1 :on-press - #(utils/expand-sheet animations expanded-height max-height overlay-z-index expanded?) + #(utils/expand-sheet animations + expanded-height + max-height + overlay-z-index + expanded? + text-sheet-lock?) :style (style/sheet-container derived)} [bar @text-height] [reanimated/linear-gradient @@ -74,5 +79,5 @@ :on-layout #(utils/on-layout % text-height)}]]]])))) (defn view - [messages {:keys [overlay-opacity]} {:keys [overlay-z-index]}] - [:f> text-sheet messages overlay-opacity overlay-z-index]) + [messages {:keys [overlay-opacity]} {:keys [overlay-z-index]} {:keys [text-sheet-lock?]}] + [:f> text-sheet messages overlay-opacity overlay-z-index text-sheet-lock?]) diff --git a/src/status_im2/contexts/chat/lightbox/utils.cljs b/src/status_im2/contexts/chat/lightbox/utils.cljs index 3aa9064cfa..3316f6e2ca 100644 --- a/src/status_im2/contexts/chat/lightbox/utils.cljs +++ b/src/status_im2/contexts/chat/lightbox/utils.cljs @@ -1,6 +1,7 @@ (ns status-im2.contexts.chat.lightbox.utils (:require [clojure.string :as string] + [oops.core :as oops] [quo2.foundations.colors :as colors] [react-native.core :as rn] [react-native.gesture :as gesture] @@ -11,7 +12,6 @@ [status-im2.contexts.chat.lightbox.animations :as anim] [status-im2.contexts.chat.lightbox.top-view :as top-view] [utils.re-frame :as rf] - [oops.core :refer [oget]] [status-im2.contexts.chat.lightbox.constants :as constants] [utils.worklets.lightbox :as worklet])) @@ -98,6 +98,20 @@ (when (and enabled? (not= result orientation/landscape-right)) (handle-orientation result props state animations)))))))) +(defn on-scroll + [e item-width {:keys [images-opacity]} landscape?] + (let [total-item-width (+ item-width constants/separator-width) + progress (/ (if landscape? + (oops/oget e "nativeEvent.contentOffset.y") + (oops/oget e "nativeEvent.contentOffset.x")) + total-item-width) + index-initial (max (Math/floor progress) 0) + index-final (inc index-initial) + decimal-part (- progress index-initial)] + (anim/set-val (nth images-opacity index-initial) (- 1 decimal-part)) + (when (< index-final (count images-opacity)) + (anim/set-val (nth images-opacity index-final) decimal-part)))) + (defn drag-gesture [{:keys [pan-x pan-y background-color opacity layout]} x? set-full-height?] (-> @@ -105,14 +119,15 @@ (gesture/enabled true) (gesture/max-pointers 1) (gesture/on-start #(reset! set-full-height? false)) - (gesture/on-update (fn [e] - (let [translation (if x? (oget e "translationX") (oget e "translationY")) - progress (Math/abs (/ translation constants/drag-threshold))] - (anim/set-val (if x? pan-x pan-y) translation) - (anim/set-val opacity (- 1 progress)) - (anim/set-val layout (* progress -20))))) + (gesture/on-update + (fn [e] + (let [translation (if x? (oops/oget e "translationX") (oops/oget e "translationY")) + progress (Math/abs (/ translation constants/drag-threshold))] + (anim/set-val (if x? pan-x pan-y) translation) + (anim/set-val opacity (- 1 progress)) + (anim/set-val layout (* progress -20))))) (gesture/on-end (fn [e] - (if (> (Math/abs (if x? (oget e "translationX") (oget e "translationY"))) + (if (> (Math/abs (if x? (oops/oget e "translationX") (oops/oget e "translationY"))) constants/drag-threshold) (do (anim/animate background-color "rgba(0,0,0,0)") @@ -129,6 +144,7 @@ {:flat-list-ref (atom nil) :small-list-ref (atom nil) :scroll-index-lock? (atom true) + :text-sheet-lock? (atom false) :timers (atom {})}) (defn init-state @@ -142,20 +158,29 @@ :set-full-height? (reagent/atom false) :overlay-z-index (reagent/atom 0)}) +(defn initialize-opacity + [size selected-index] + (mapv #(if (= % selected-index) + (anim/use-val 1) + (anim/use-val 0)) + (range size))) + (defn init-animations - [] - {:background-color (anim/use-val colors/neutral-100-opa-0) - :border (anim/use-val (if platform/ios? 0 16)) - :opacity (anim/use-val 0) - :overlay-opacity (anim/use-val 0) - :rotate (anim/use-val "0deg") - :layout (anim/use-val -10) - :top-view-y (anim/use-val 0) - :top-view-x (anim/use-val 0) - :top-view-width (anim/use-val (:width (rn/get-window))) - :top-view-bg (anim/use-val colors/neutral-100-opa-0) - :pan-y (anim/use-val 0) - :pan-x (anim/use-val 0)}) + [size index] + {:background-color (anim/use-val colors/neutral-100-opa-0) + :border (anim/use-val (if platform/ios? 0 16)) + :full-screen-scale (anim/use-val 1) + :opacity (anim/use-val 0) + :overlay-opacity (anim/use-val 0) + :images-opacity (initialize-opacity size index) + :rotate (anim/use-val "0deg") + :layout (anim/use-val -10) + :top-view-y (anim/use-val 0) + :top-view-x (anim/use-val 0) + :top-view-width (anim/use-val (:width (rn/get-window))) + :top-view-bg (anim/use-val colors/neutral-100-opa-0) + :pan-y (anim/use-val 0) + :pan-x (anim/use-val 0)}) (defn init-derived-animations [{:keys [layout]}] diff --git a/src/status_im2/contexts/chat/lightbox/view.cljs b/src/status_im2/contexts/chat/lightbox/view.cljs index a2a9155b0b..a38233948b 100644 --- a/src/status_im2/contexts/chat/lightbox/view.cljs +++ b/src/status_im2/contexts/chat/lightbox/view.cljs @@ -1,6 +1,7 @@ (ns status-im2.contexts.chat.lightbox.view (:require [clojure.string :as string] + [oops.core :as oops] [quo2.foundations.colors :as colors] [react-native.core :as rn] [react-native.orientation :as orientation] @@ -14,7 +15,6 @@ [status-im2.contexts.chat.lightbox.zoomable-image.view :as zoomable-image] [status-im2.contexts.chat.lightbox.top-view :as top-view] [status-im2.contexts.chat.lightbox.bottom-view :as bottom-view] - [oops.core :refer [oget]] [status-im2.contexts.chat.lightbox.utils :as utils] [status-im2.contexts.chat.lightbox.constants :as constants])) @@ -28,12 +28,12 @@ (defn on-viewable-items-changed [e {:keys [scroll-index-lock? small-list-ref]} {:keys [scroll-index]}] (when-not @scroll-index-lock? - (let [changed (-> e (oget :changed) first) - index (oget changed :index)] + (let [changed (-> e (oops/oget :changed) first) + index (oops/oget changed :index)] (reset! scroll-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))])))) + (rf/dispatch [:chat.ui/update-shared-element-id (:message-id (oops/oget changed :item))])))) (defn image [message index _ {:keys [screen-width screen-height] :as args}] @@ -79,19 +79,23 @@ [gesture/flat-list {:ref #(reset! (:flat-list-ref props) %) :key-fn :message-id + :on-scroll #(utils/on-scroll % item-width animations landscape?) + :scroll-event-throttle 8 :style {:width (+ screen-width constants/separator-width)} :data @data :render-fn image - :render-data {:opacity-value (:opacity animations) - :border-value (:border animations) - :transparent? transparent? - :set-full-height? set-full-height? - :screen-height screen-height - :screen-width screen-width - :window-height window-height - :window-width window-width - :props props - :curr-orientation curr-orientation} + :render-data {:opacity-value (:opacity animations) + :border-value (:border animations) + :full-screen-scale (:full-screen-scale animations) + :images-opacity (:images-opacity animations) + :transparent? transparent? + :set-full-height? set-full-height? + :screen-height screen-height + :screen-width screen-width + :window-height window-height + :window-width window-width + :props props + :curr-orientation curr-orientation} :horizontal horizontal? :inverted inverted? :paging-enabled true @@ -113,7 +117,7 @@ handle-items-changed (fn [e] (on-viewable-items-changed e props state))] (fn [] - (let [animations (utils/init-animations) + (let [animations (utils/init-animations (count messages) index) derived (utils/init-derived-animations animations)] (anim/animate (:background-color animations) colors/neutral-100) (reset! (:data state) messages) diff --git a/src/status_im2/contexts/chat/lightbox/zoomable_image/constants.cljs b/src/status_im2/contexts/chat/lightbox/zoomable_image/constants.cljs index 3eb432074a..5f06c5c76b 100644 --- a/src/status_im2/contexts/chat/lightbox/zoomable_image/constants.cljs +++ b/src/status_im2/contexts/chat/lightbox/zoomable_image/constants.cljs @@ -15,3 +15,5 @@ (def ^:const default-duration 300) (def ^:const default-dimension 1000) + +(def ^:const margin 8) diff --git a/src/status_im2/contexts/chat/lightbox/zoomable_image/style.cljs b/src/status_im2/contexts/chat/lightbox/zoomable_image/style.cljs index e1a7d3939a..55649d9efe 100644 --- a/src/status_im2/contexts/chat/lightbox/zoomable_image/style.cljs +++ b/src/status_im2/contexts/chat/lightbox/zoomable_image/style.cljs @@ -6,6 +6,7 @@ (defn container [{:keys [width height]} {:keys [pan-x pan-y pinch-x pinch-y scale]} + full-screen-scale set-full-height? portrait?] (reanimated/apply-animations-to-style @@ -13,7 +14,8 @@ {:translateY pan-y} {:translateX pinch-x} {:translateY pinch-y} - {:scale scale}]} + {:scale scale} + {:scale full-screen-scale}]} {:justify-content :center :align-items :center :width (if (or platform/ios? portrait?) width "100%") @@ -22,10 +24,12 @@ (defn image [{:keys [image-width image-height]} {:keys [rotate rotate-scale]} - border-radius] + {:keys [border-value images-opacity]} + index] (reanimated/apply-animations-to-style {:transform [{:rotate rotate} {:scale rotate-scale}] - :border-radius border-radius} + :opacity (nth images-opacity index) + :border-radius border-value} {:width image-width :height image-height})) diff --git a/src/status_im2/contexts/chat/lightbox/zoomable_image/utils.cljs b/src/status_im2/contexts/chat/lightbox/zoomable_image/utils.cljs index a06590a1ea..f21a0839db 100644 --- a/src/status_im2/contexts/chat/lightbox/zoomable_image/utils.cljs +++ b/src/status_im2/contexts/chat/lightbox/zoomable_image/utils.cljs @@ -1,31 +1,32 @@ (ns status-im2.contexts.chat.lightbox.zoomable-image.utils (:require [clojure.string :as string] + [react-native.core :as rn] [react-native.navigation :as navigation] [react-native.orientation :as orientation] [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 constants] [status-im2.contexts.chat.lightbox.animations :as anim] [utils.re-frame :as rf])) ;;; Helpers (defn center-x [{:keys [pinch-x pinch-x-start pan-x pan-x-start]} exit?] - (let [duration (if exit? 100 c/default-duration)] - (anim/animate pinch-x c/init-offset duration) - (anim/set-val pinch-x-start c/init-offset) - (anim/animate pan-x c/init-offset duration) - (anim/set-val pan-x-start c/init-offset))) + (let [duration (if exit? 100 constants/default-duration)] + (anim/animate pinch-x constants/init-offset duration) + (anim/set-val pinch-x-start constants/init-offset) + (anim/animate pan-x constants/init-offset duration) + (anim/set-val pan-x-start constants/init-offset))) (defn center-y [{:keys [pinch-y pinch-y-start pan-y pan-y-start]} exit?] - (let [duration (if exit? 100 c/default-duration)] - (anim/animate pinch-y c/init-offset duration) - (anim/set-val pinch-y-start c/init-offset) - (anim/animate pan-y c/init-offset duration) - (anim/set-val pan-y-start c/init-offset))) + (let [duration (if exit? 100 constants/default-duration)] + (anim/animate pinch-y constants/init-offset duration) + (anim/set-val pinch-y-start constants/init-offset) + (anim/animate pan-y constants/init-offset duration) + (anim/set-val pan-y-start constants/init-offset))) (defn reset-values [exit? animations {:keys [focal-x focal-y]}] @@ -40,9 +41,9 @@ {:keys [x-threshold-scale y-threshold-scale]} {:keys [scale saved-scale] :as animations} {:keys [pan-x-enabled? pan-y-enabled?] :as state}] - (when (= value c/min-scale) + (when (= value constants/min-scale) (reset-values exit? animations state)) - (anim/animate scale value (if exit? 100 c/default-duration)) + (anim/animate scale value (if exit? 100 constants/default-duration)) (anim/set-val saved-scale value) (reset! pan-x-enabled? (> value x-threshold-scale)) (reset! pan-y-enabled? (> value y-threshold-scale))) @@ -66,8 +67,8 @@ (anim/animate rotate-scale landscape-scale-val)) (= curr-orientation orientation/portrait) (do - (anim/animate rotate c/init-rotation) - (anim/animate rotate-scale c/min-scale))) + (anim/animate rotate constants/init-rotation) + (anim/animate rotate-scale constants/min-scale))) (cond (= curr-orientation orientation/landscape-left) (do @@ -79,8 +80,8 @@ (anim/set-val rotate-scale landscape-scale-val)) (= curr-orientation orientation/portrait) (do - (anim/set-val rotate c/init-rotation) - (anim/set-val rotate-scale c/min-scale)))) + (anim/set-val rotate constants/init-rotation) + (anim/set-val rotate-scale constants/min-scale)))) (center-x animations false) (center-y animations false) (reset! pan-x-enabled? (> (anim/get-val scale) x-threshold-scale)) @@ -92,9 +93,9 @@ [exit-lightbox-signal index scale rescale set-full-height?] (when (= exit-lightbox-signal index) (reset! set-full-height? false) - (if (> scale c/min-scale) + (if (> scale constants/min-scale) (do - (rescale c/min-scale true) + (rescale constants/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))) @@ -102,13 +103,16 @@ (defn handle-zoom-out-signal "Zooms out when pressing on another photo from the small bottom list" [zoom-out-signal index scale rescale] - (when (and (= zoom-out-signal index) (> scale c/min-scale)) - (rescale c/min-scale true))) + (when (and (= zoom-out-signal index) (> scale constants/min-scale)) + (rescale constants/min-scale true))) (defn toggle-opacity - [index {:keys [opacity-value border-value transparent? props]} portrait?] - (let [{:keys [small-list-ref timers]} props - opacity (reanimated/get-shared-value opacity-value)] + [index {:keys [opacity-value border-value full-screen-scale transparent? props]} portrait?] + (let [{:keys [small-list-ref timers text-sheet-lock?]} props + opacity (reanimated/get-shared-value opacity-value) + scale-value (+ 1 + (/ constants/margin + (:width (rn/get-window))))] (if (= opacity 1) (do (js/clearTimeout (:show-0 @timers)) @@ -119,7 +123,9 @@ (js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible false}}) (if platform/ios? 75 0))) (anim/animate opacity-value 0) - (swap! timers assoc :hide-1 (js/setTimeout #(reset! transparent? (not @transparent?)) 400))) + (swap! timers assoc :hide-1 (js/setTimeout #(reset! transparent? (not @transparent?)) 400)) + (reset! text-sheet-lock? true) + (js/setTimeout #(reset! text-sheet-lock? false) 300)) (do (js/clearTimeout (:hide-0 @timers)) (js/clearTimeout (:hide-1 @timers)) @@ -135,7 +141,8 @@ :show-2 (js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible true}}) (if platform/ios? 150 50)))))) - (anim/animate border-value (if (= opacity 1) 0 16)))) + (anim/animate border-value (if (= opacity 1) 0 16)) + (anim/animate full-screen-scale (if (= opacity 1) scale-value 1)))) ;;; Dimensions (defn get-dimensions @@ -150,11 +157,12 @@ landscape-image-width (* pixels-width (/ window-width pixels-height)) width (if landscape? landscape-image-width portrait-image-width) height (if landscape? screen-height portrait-image-height) - container-width (if platform/ios? window-width width) - container-height (if (and platform/ios? landscape?) landscape-image-width height)] + container-width (- (if platform/ios? window-width width) constants/margin) + container-height (- (if (and platform/ios? landscape?) landscape-image-width height) + constants/margin)] ;; width and height used in style prop - {:image-width (if platform/ios? portrait-image-width width) - :image-height (if platform/ios? portrait-image-height height) + {:image-width (- (if platform/ios? portrait-image-width width) constants/margin) + :image-height (- (if platform/ios? portrait-image-height height) constants/margin) ;; container width and height, also used in animations calculations :width container-width :height container-height @@ -169,7 +177,7 @@ ;;; MATH (defn get-max-offset [size screen-size scale] - (/ (- (* size (min scale c/max-scale)) + (/ (- (* size (min scale constants/max-scale)) screen-size) 2)) @@ -181,8 +189,8 @@ (defn get-double-tap-offset [size screen-size focal] (let [center (/ size 2) - target-point (* (- center focal) c/double-tap-scale) - max-offset (get-max-offset size screen-size c/double-tap-scale) + target-point (* (- center focal) constants/double-tap-scale) + max-offset (get-max-offset size screen-size constants/double-tap-scale) translate-val (min (Math/abs target-point) max-offset)] (if (neg? target-point) (- translate-val) translate-val))) @@ -201,20 +209,20 @@ ;;; 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) + {:scale (anim/use-val constants/min-scale) + :saved-scale (anim/use-val constants/min-scale) + :pan-x-start (anim/use-val constants/init-offset) + :pan-x (anim/use-val constants/init-offset) + :pan-y-start (anim/use-val constants/init-offset) + :pan-y (anim/use-val constants/init-offset) + :pinch-x-start (anim/use-val constants/init-offset) + :pinch-x (anim/use-val constants/init-offset) + :pinch-y-start (anim/use-val constants/init-offset) + :pinch-y (anim/use-val constants/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)}) + :rotate (anim/use-val constants/init-rotation) + :rotate-scale (anim/use-val constants/min-scale)}) (defn init-state [] diff --git a/src/status_im2/contexts/chat/lightbox/zoomable_image/view.cljs b/src/status_im2/contexts/chat/lightbox/zoomable_image/view.cljs index 705466ce93..1394cb7ca6 100644 --- a/src/status_im2/contexts/chat/lightbox/zoomable_image/view.cljs +++ b/src/status_im2/contexts/chat/lightbox/zoomable_image/view.cljs @@ -221,12 +221,13 @@ [reanimated/view {:style (style/container dimensions animations + (:full-screen-scale render-data) @set-full-height? (= curr-orientation orientation/portrait))} [reanimated/fast-image {:source {:uri (http/replace-port (:image content) (rf/sub [:mediaserver/port]))} :native-ID (when focused? :shared-element) - :style (style/image dimensions animations (:border-value render-data))}]]])) + :style (style/image dimensions animations render-data index)}]]])) (defn zoomable-image []