Lightbox opacity swipe animation, and margins (#16510)
* feat: lightbox animations
This commit is contained in:
parent
f5948fa016
commit
7392b9bf50
|
@ -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
|
||||
|
|
|
@ -40,12 +40,13 @@
|
|||
|
||||
(defn expand-sheet
|
||||
[{:keys [derived-value overlay-opacity saved-top]}
|
||||
expanded-height max-height overlay-z-index expanded?]
|
||||
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))
|
||||
(reset! expanded? true)))
|
||||
|
||||
(defn on-scroll
|
||||
[e expanded? dragging? {:keys [gradient-opacity]}]
|
||||
|
|
|
@ -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?])
|
||||
|
|
|
@ -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"))
|
||||
(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,12 +158,21 @@
|
|||
: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
|
||||
[]
|
||||
[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)
|
||||
|
|
|
@ -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,11 +79,15 @@
|
|||
[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)
|
||||
:full-screen-scale (:full-screen-scale animations)
|
||||
:images-opacity (:images-opacity animations)
|
||||
:transparent? transparent?
|
||||
:set-full-height? set-full-height?
|
||||
:screen-height screen-height
|
||||
|
@ -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)
|
||||
|
|
|
@ -15,3 +15,5 @@
|
|||
(def ^:const default-duration 300)
|
||||
|
||||
(def ^:const default-dimension 1000)
|
||||
|
||||
(def ^:const margin 8)
|
||||
|
|
|
@ -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}))
|
||||
|
|
|
@ -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
|
||||
[]
|
||||
|
|
|
@ -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
|
||||
[]
|
||||
|
|
Loading…
Reference in New Issue