Lightbox improvements (#15243)

* feat: lightbox polishing and improvements
This commit is contained in:
Omar Basem 2023-03-06 08:35:36 +04:00 committed by GitHub
parent 80a75c5232
commit 16ffc23509
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 186 additions and 129 deletions

View File

@ -215,9 +215,9 @@
(def album-image-sizes ; sometimes we subtract 1 or 0.5 for padding (def album-image-sizes ; sometimes we subtract 1 or 0.5 for padding
{2 {0 image-size {2 {0 image-size
1 image-size} 1 image-size}
3 {0 [(* image-size 2) (* image-size 1.5)] 3 {0 [(* image-size 2) (* image-size 1.25)]
1 [(- image-size 0.5) (- (* image-size 0.67) 1)] 1 [(- image-size 0.5) (- (* image-size 0.75) 1)]
2 [(- image-size 0.5) (- (* image-size 0.67) 1)]} 2 [(- image-size 0.5) (- (* image-size 0.75) 1)]}
4 {0 image-size 4 {0 image-size
1 image-size 1 image-size
2 image-size 2 image-size

View File

@ -353,3 +353,8 @@
[{:keys [db]} value] [{:keys [db]} value]
{:db (assoc db :lightbox/orientation value)}) {:db (assoc db :lightbox/orientation value)})
(rf/defn lightbox-scale
{:events [:chat.ui/lightbox-scale]}
[{:keys [db]} value]
{:db (assoc db :lightbox/scale value)})

View File

@ -61,8 +61,8 @@
:start {:x 0 :y 1} :start {:x 0 :y 1}
:end {:x 0 :y 0} :end {:x 0 :y 0}
:style (style/gradient-container insets animations)} :style (style/gradient-container insets animations)}
[rn/text (when (not= text "placeholder")
{:style style/text-style} text] [rn/text {:style style/text-style} text])
[rn/flat-list [rn/flat-list
{:ref #(reset! (:small-list-ref atoms) %) {:ref #(reset! (:small-list-ref atoms) %)
:key-fn :message-id :key-fn :message-id
@ -72,7 +72,9 @@
:render-data {:scroll-index scroll-index :render-data {:scroll-index scroll-index
:atoms atoms} :atoms atoms}
:horizontal true :horizontal true
:shows-horizontal-scroll-indicator false
:get-item-layout get-small-item-layout :get-item-layout get-small-item-layout
:separator [rn/view {:style {:width 8}}] :separator [rn/view {:style {:width 8}}]
:initial-scroll-index index :initial-scroll-index index
:content-container-style (style/content-container padding-horizontal)}]]))]) :content-container-style (style/content-container padding-horizontal)}]]))])

View File

@ -10,19 +10,19 @@
;;;; TOP-VIEW ;;;; TOP-VIEW
(defn top-view-container (defn top-view-container
[top-inset {:keys [opacity rotate top-view-y top-view-x top-view-width top-view-bg]} window-width [top-inset {:keys [opacity rotate top-view-y top-view-x top-view-width top-view-bg top-layout]}
window-width
bg-color] bg-color]
(reanimated/apply-animations-to-style (reanimated/apply-animations-to-style
(if platform/ios? (if platform/ios?
{:transform [{:rotate rotate} {:transform [{:translateY top-layout}
{:rotate rotate}
{:translateY top-view-y} {:translateY top-view-y}
{:translateX top-view-x}] {:translateX top-view-x}]
:opacity opacity :opacity opacity
:width top-view-width :width top-view-width
:background-color top-view-bg} :background-color top-view-bg}
{:transform [{:rotate rotate} {:transform [{:translateY top-layout}]
{:translateY top-view-y}
{:translateX top-view-x}]
:opacity opacity}) :opacity opacity})
{:position :absolute {:position :absolute
:padding-horizontal 20 :padding-horizontal 20
@ -48,9 +48,10 @@
;;;; BOTTOM-VIEW ;;;; BOTTOM-VIEW
(defn gradient-container (defn gradient-container
[insets {:keys [opacity]}] [insets {:keys [opacity bottom-layout]}]
(reanimated/apply-animations-to-style (reanimated/apply-animations-to-style
{:opacity opacity} {:transform [{:translateY bottom-layout}]
:opacity opacity}
{:position :absolute {:position :absolute
:bottom 0 :bottom 0
:padding-bottom (:bottom insets) :padding-bottom (:bottom insets)

View File

@ -50,9 +50,11 @@
{:style {:flex-direction :row {:style {:flex-direction :row
:align-items :center}} :align-items :center}}
[rn/touchable-opacity [rn/touchable-opacity
{:on-press #(rf/dispatch (if platform/ios? {:on-press (fn []
(common/set-val-timing (:opacity animations) 0)
(rf/dispatch (if platform/ios?
[:chat.ui/exit-lightbox-signal @index] [:chat.ui/exit-lightbox-signal @index]
[:navigate-back])) [:navigate-back])))
:style style/close-container} :style style/close-container}
[quo/icon :close {:size 20 :color colors/white}]] [quo/icon :close {:size 20 :color colors/white}]]
[rn/view {:style {:margin-left 12}} [rn/view {:style {:margin-left 12}}

View File

@ -94,7 +94,7 @@
(let [{:keys [messages index]} (rf/sub [:get-screen-params]) (let [{:keys [messages index]} (rf/sub [:get-screen-params])
atoms {:flat-list-ref (atom nil) atoms {:flat-list-ref (atom nil)
:small-list-ref (atom nil) :small-list-ref (atom nil)
:scroll-index-lock? (atom false) :scroll-index-lock? (atom true)
:insets-atom (atom nil)} :insets-atom (atom nil)}
;; The initial value of data is the image that was pressed (and not the whole album) in order ;; The initial value of data is the image that was pressed (and not the whole album) in order
;; for the transition animation to execute properly, otherwise it would animate towards ;; for the transition animation to execute properly, otherwise it would animate towards
@ -103,9 +103,11 @@
scroll-index (reagent/atom index) scroll-index (reagent/atom index)
transparent? (reagent/atom false) transparent? (reagent/atom false)
window (rf/sub [:dimensions/window]) window (rf/sub [:dimensions/window])
animations {:border (common/use-val 12) animations {:border (common/use-val (if platform/ios? 0 12))
:opacity (common/use-val 1) :opacity (common/use-val 0)
:rotate (common/use-val "0deg") :rotate (common/use-val "0deg")
:top-layout (common/use-val -10)
:bottom-layout (common/use-val 10)
:top-view-y (common/use-val 0) :top-view-y (common/use-val 0)
:top-view-x (common/use-val 0) :top-view-x (common/use-val 0)
:top-view-width (common/use-val (:width window)) :top-view-width (common/use-val (:width window))
@ -124,11 +126,21 @@
;; RNN does not support landscape-right ;; RNN does not support landscape-right
(when (and enabled? (not= result orientation/landscape-right)) (when (and enabled? (not= result orientation/landscape-right))
(handle-orientation result scroll-index window animations atoms))))))) (handle-orientation result scroll-index window animations atoms)))))))
(rn/use-effect-once (fn [] (rn/use-effect (fn []
(when @(:flat-list-ref atoms) (when @(:flat-list-ref atoms)
(.scrollToIndex ^js @(:flat-list-ref atoms) (.scrollToIndex ^js @(:flat-list-ref atoms)
#js {:animated false :index index})) #js {:animated false :index index}))
js/undefined)) (js/setTimeout (fn []
(common/set-val-timing (:opacity animations) 1)
(common/set-val-timing (:top-layout animations) 0)
(common/set-val-timing (:bottom-layout animations) 0)
(common/set-val-timing (:border animations) 12))
(if platform/ios? 250 100))
(js/setTimeout #(reset! (:scroll-index-lock? atoms) false) 300)
(fn []
(rf/dispatch [:chat.ui/zoom-out-signal nil])
(when platform/android?
(rf/dispatch [:chat.ui/lightbox-scale 1])))))
[safe-area/consumer [safe-area/consumer
(fn [insets] (fn [insets]
(let [curr-orientation (or (rf/sub [:lightbox/orientation]) orientation/portrait) (let [curr-orientation (or (rf/sub [:lightbox/orientation]) orientation/portrait)

View File

@ -1,9 +1,12 @@
(ns status-im2.contexts.chat.lightbox.zoomable-image.style (ns status-im2.contexts.chat.lightbox.zoomable-image.style
(:require [react-native.reanimated :as reanimated])) (:require
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]))
(defn container (defn container
[{:keys [width height]} [{:keys [width height]}
{:keys [pan-x pan-y pinch-x pinch-y scale]}] {:keys [pan-x pan-y pinch-x pinch-y scale]}
set-full-height?]
(reanimated/apply-animations-to-style (reanimated/apply-animations-to-style
{:transform [{:translateX pan-x} {:transform [{:translateX pan-x}
{:translateY pan-y} {:translateY pan-y}
@ -12,8 +15,8 @@
{:scale scale}]} {:scale scale}]}
{:justify-content :center {:justify-content :center
:align-items :center :align-items :center
:width width :width (if platform/ios? width "100%")
:height height})) :height (if set-full-height? "100%" height)}))
(defn image (defn image
[{:keys [image-width image-height]} [{:keys [image-width image-height]}

View File

@ -42,8 +42,9 @@
(defn handle-exit-lightbox-signal (defn handle-exit-lightbox-signal
"On ios, when attempting to navigate back while zoomed in, the shared-element transition animation "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" doesn't execute properly, so we need to zoom out first"
[exit-lightbox-signal index scale rescale] [exit-lightbox-signal index scale rescale set-full-height?]
(when (= exit-lightbox-signal index) (when (= exit-lightbox-signal index)
(reset! set-full-height? false)
(if (> scale c/min-scale) (if (> scale c/min-scale)
(do (do
(rescale c/min-scale true) (rescale c/min-scale true)
@ -81,3 +82,11 @@
(defn get-pinch-position (defn get-pinch-position
[scale-diff size focal] [scale-diff size focal]
(* (- (/ size 2) focal) scale-diff)) (* (- (/ size 2) focal) scale-diff))
(defn get-focal
[focal size screen-size]
(let [min (/ (- screen-size size) 2)
max (+ min size)]
(if (or (> focal max) (< focal min))
(/ screen-size 2)
focal)))

View File

@ -1,5 +1,6 @@
(ns status-im2.contexts.chat.lightbox.zoomable-image.view (ns status-im2.contexts.chat.lightbox.zoomable-image.view
(:require (:require
[react-native.core :as rn]
[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]
@ -73,7 +74,9 @@
(when (= value c/min-scale) (when (= value c/min-scale)
(reset-values exit? animations props)) (reset-values exit? animations props))
(reset! pan-x-enabled? (> value x-threshold-scale)) (reset! pan-x-enabled? (> value x-threshold-scale))
(reset! pan-y-enabled? (> value y-threshold-scale))) (reset! pan-y-enabled? (> value y-threshold-scale))
(when platform/android?
(rf/dispatch [:chat.ui/lightbox-scale value])))
(defn handle-orientation-change (defn handle-orientation-change
[curr-orientation [curr-orientation
@ -128,28 +131,58 @@
(rescale c/double-tap-scale)) (rescale c/double-tap-scale))
(rescale c/min-scale)))))) (rescale c/min-scale))))))
(defn pinch-gesture ;; not using on-finalize because on-finalize gets called always regardless the gesture executed or not
(defn finalize-pinch
[{:keys [width height screen-height screen-width x-threshold-scale y-threshold-scale]} [{:keys [width height screen-height screen-width x-threshold-scale y-threshold-scale]}
{:keys [saved-scale scale pinch-x pinch-y pinch-x-start pinch-y-start pinch-x-max pinch-y-max pan-y {:keys [saved-scale scale pinch-x pinch-y pinch-x-start pinch-y-start pan-y pan-y-start pan-x
pan-y-start pan-x pan-x-start] pan-x-start]}
{:keys [pan-x-enabled? pan-y-enabled?]}]
(let [curr-offset-y (+ (get-val pan-y) (get-val pinch-y))
max-offset-y (utils/get-max-offset height screen-height (get-val scale))
max-offset-y (if (neg? curr-offset-y) (- max-offset-y) max-offset-y)
curr-offset-x (+ (get-val pan-x) (get-val pinch-x))
max-offset-x (utils/get-max-offset width screen-width (get-val scale))
max-offset-x (if (neg? curr-offset-x) (- max-offset-x) max-offset-x)]
(when (and (> (get-val scale) y-threshold-scale)
(< (get-val scale) c/max-scale)
(> (Math/abs curr-offset-y) (Math/abs max-offset-y)))
(set-val pinch-y (timing c/init-offset))
(set-val pinch-y-start c/init-offset)
(set-val pan-y (timing max-offset-y))
(set-val pan-y-start max-offset-y))
(when (and (> (get-val scale) x-threshold-scale)
(< (get-val scale) c/max-scale)
(> (Math/abs curr-offset-x) (Math/abs max-offset-x)))
(set-val pinch-x (timing c/init-offset))
(set-val pinch-x-start c/init-offset)
(set-val pan-x (timing max-offset-x))
(set-val pan-x-start max-offset-x))
(reset! pan-x-enabled? (> (get-val scale) x-threshold-scale))
(reset! pan-y-enabled? (> (get-val scale) y-threshold-scale))
(when platform/android?
(rf/dispatch [:chat.ui/lightbox-scale (get-val saved-scale)]))))
(defn pinch-gesture
[{: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]
:as animations} :as animations}
{:keys [pan-x-enabled? pan-y-enabled? focal-x focal-y]} {:keys [focal-x focal-y] :as props}
rescale] rescale]
(-> (->
(gesture/gesture-pinch) (gesture/gesture-pinch)
(gesture/on-begin (fn [e] (gesture/on-begin (fn [e]
(when platform/ios? (when platform/ios?
(reset! focal-x (oget e "focalX")) (reset! focal-x (oget e "focalX"))
(reset! focal-y (oget e "focalY"))))) (reset! focal-y (utils/get-focal (oget e "focalY") height screen-height)))))
(gesture/on-start (fn [e] (gesture/on-start (fn [e]
(when platform/android? (when platform/android?
(reset! focal-x (oget e "focalX")) (reset! focal-x (utils/get-focal (oget e "focalX") width screen-width))
(reset! focal-y (oget e "focalY"))))) (reset! focal-y (utils/get-focal (oget e "focalY") height screen-height)))))
(gesture/on-update (fn [e] (gesture/on-update (fn [e]
(let [new-scale (* (oget e "scale") (get-val saved-scale)) (let [new-scale (* (oget e "scale") (get-val saved-scale))
scale-diff (utils/get-scale-diff new-scale (get-val saved-scale)) scale-diff (utils/get-scale-diff new-scale (get-val saved-scale))
new-pinch-x (utils/get-pinch-position scale-diff width @focal-x) new-pinch-x (utils/get-pinch-position scale-diff screen-width @focal-x)
new-pinch-y (utils/get-pinch-position scale-diff height @focal-y)] new-pinch-y (utils/get-pinch-position scale-diff screen-height @focal-y)]
(when (and (>= new-scale c/max-scale) (= (get-val pinch-x-max) js/Infinity)) (when (and (>= new-scale c/max-scale) (= (get-val pinch-x-max) js/Infinity))
(set-val pinch-x-max (get-val pinch-x)) (set-val pinch-x-max (get-val pinch-x))
(set-val pinch-y-max (get-val pinch-y))) (set-val pinch-y-max (get-val pinch-y)))
@ -179,29 +212,8 @@
(when (< (get-val scale) x-threshold-scale) (when (< (get-val scale) x-threshold-scale)
(center-x animations false)) (center-x animations false))
(when (< (get-val scale) y-threshold-scale) (when (< (get-val scale) y-threshold-scale)
(center-y animations false)))))) (center-y animations false))))
(gesture/on-finalize (finalize-pinch dimensions animations props)))))
(fn []
(let [curr-offset-y (+ (get-val pan-y) (get-val pinch-y))
max-offset-y (utils/get-max-offset height screen-height (get-val scale))
max-offset-y (if (neg? curr-offset-y) (- max-offset-y) max-offset-y)
curr-offset-x (+ (get-val pan-x) (get-val pinch-x))
max-offset-x (utils/get-max-offset width screen-width (get-val scale))
max-offset-x (if (neg? curr-offset-x) (- max-offset-x) max-offset-x)]
(when (and (> (get-val scale) y-threshold-scale)
(> (Math/abs curr-offset-y) (Math/abs max-offset-y)))
(set-val pinch-y (timing c/init-offset))
(set-val pinch-y-start c/init-offset)
(set-val pan-y (timing max-offset-y))
(set-val pan-y-start max-offset-y))
(when (and (> (get-val scale) x-threshold-scale)
(> (Math/abs curr-offset-x) (Math/abs max-offset-x)))
(set-val pinch-x (timing c/init-offset))
(set-val pinch-x-start c/init-offset)
(set-val pan-x (timing max-offset-x))
(set-val pan-x-start max-offset-x))
(reset! pan-x-enabled? (> (get-val scale) x-threshold-scale))
(reset! pan-y-enabled? (> (get-val scale) y-threshold-scale)))))))
(defn pan-x-gesture (defn pan-x-gesture
[{:keys [width screen-width x-threshold-scale]} [{:keys [width screen-width x-threshold-scale]}
@ -272,16 +284,18 @@
;;;; Finally, the component ;;;; Finally, the component
(defn zoomable-image (defn zoomable-image
[{:keys [image-width image-height content message-id]} index border-radius on-tap] [{:keys [image-width image-height content message-id]} index border-radius on-tap]
(let [set-full-height? (reagent/atom false)]
[:f> [:f>
(fn [] (fn []
(let [shared-element-id (rf/sub [:shared-element-id]) (let [shared-element-id (rf/sub [:shared-element-id])
exit-lightbox-signal (rf/sub [:lightbox/exit-signal]) exit-lightbox-signal (rf/sub [:lightbox/exit-signal])
zoom-out-signal (rf/sub [:lightbox/zoom-out-signal]) zoom-out-signal (rf/sub [:lightbox/zoom-out-signal])
initial-scale (if platform/ios? c/min-scale (rf/sub [:lightbox/scale]))
curr-orientation (or (rf/sub [:lightbox/orientation]) orientation/portrait) curr-orientation (or (rf/sub [:lightbox/orientation]) orientation/portrait)
focused? (= shared-element-id message-id) focused? (= shared-element-id message-id)
dimensions (utils/get-dimensions image-width image-height curr-orientation) dimensions (utils/get-dimensions image-width image-height curr-orientation)
animations {:scale (use-val c/min-scale) animations {:scale (use-val initial-scale)
:saved-scale (use-val c/min-scale) :saved-scale (use-val initial-scale)
:pan-x-start (use-val c/init-offset) :pan-x-start (use-val c/init-offset)
:pan-x (use-val c/init-offset) :pan-x (use-val c/init-offset)
:pan-y-start (use-val c/init-offset) :pan-y-start (use-val c/init-offset)
@ -300,12 +314,16 @@
:focal-y (reagent/atom nil)} :focal-y (reagent/atom nil)}
rescale (fn [value exit?] rescale (fn [value exit?]
(rescale-image value exit? dimensions animations props))] (rescale-image value exit? dimensions animations props))]
(rn/use-effect-once (fn []
(js/setTimeout #(reset! set-full-height? true) 500)
js/undefined))
(when platform/ios? (when platform/ios?
(handle-orientation-change curr-orientation focused? dimensions animations props) (handle-orientation-change curr-orientation focused? dimensions animations props)
(utils/handle-exit-lightbox-signal exit-lightbox-signal (utils/handle-exit-lightbox-signal exit-lightbox-signal
index index
(get-val (:scale animations)) (get-val (:scale animations))
rescale)) rescale
set-full-height?))
(utils/handle-zoom-out-signal zoom-out-signal index (get-val (:scale animations)) rescale) (utils/handle-zoom-out-signal zoom-out-signal index (get-val (:scale animations)) rescale)
[:f> [:f>
(fn [] (fn []
@ -319,8 +337,9 @@
(gesture/exclusive double-tap tap))] (gesture/exclusive double-tap tap))]
[gesture/gesture-detector {:gesture composed-gestures} [gesture/gesture-detector {:gesture composed-gestures}
[reanimated/view [reanimated/view
{:style (style/container dimensions animations)} {:style (style/container dimensions animations @set-full-height?)}
[reanimated/fast-image [reanimated/fast-image
{:source {:uri (:image content)} {:source {:uri (:image content)}
:native-ID (when focused? :shared-element) :native-ID (when focused? :shared-element)
:style (style/image dimensions animations border-radius)}]]]))]))]) :style (style/image dimensions animations border-radius)}]]]))]))]))

View File

@ -45,7 +45,6 @@
[rn/touchable-opacity [rn/touchable-opacity
{:key (:message-id item) {:key (:message-id item)
:active-opacity 1 :active-opacity 1
;; issue: https://github.com/status-im/status-mobile/issues/14995
:on-long-press #(on-long-press message context) :on-long-press #(on-long-press message context)
:on-press (fn [] :on-press (fn []
(rf/dispatch [:chat.ui/update-shared-element-id (:message-id item)]) (rf/dispatch [:chat.ui/update-shared-element-id (:message-id item)])

View File

@ -21,7 +21,7 @@
[rn/touchable-opacity [rn/touchable-opacity
{:active-opacity 1 {:active-opacity 1
:key message-id :key message-id
:style {:margin-top (when (> index 0) 20)} :style {:margin-top (when (> index 0) 10)}
:on-long-press on-long-press :on-long-press on-long-press
:on-press (fn [] :on-press (fn []
(rf/dispatch [:chat.ui/update-shared-element-id message-id]) (rf/dispatch [:chat.ui/update-shared-element-id message-id])

View File

@ -44,11 +44,13 @@
:navigationBar {:backgroundColor colors/black-persist} :navigationBar {:backgroundColor colors/black-persist}
:animations {:push {:sharedElementTransitions [{:fromId :shared-element :animations {:push {:sharedElementTransitions [{:fromId :shared-element
:toId :shared-element :toId :shared-element
:interpolation {:type :decelerate}}]} :interpolation {:type :decelerate
:factor 1.5}}]}
:pop {:sharedElementTransitions [{:fromId :shared-element :pop {:sharedElementTransitions [{:fromId :shared-element
:toId :shared-element :toId :shared-element
:interpolation {:type :interpolation {:type
:decelerate}}]}}} :decelerate
:factor 1.5}}]}}}
:component lightbox/lightbox} :component lightbox/lightbox}
{:name :photo-selector {:name :photo-selector
:options {:topBar {:visible false}} :options {:topBar {:visible false}}

View File

@ -115,9 +115,12 @@
(reg-root-key-sub :chats/mention-suggestions :chats/mention-suggestions) (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 :chat/inputs-with-mentions :chat/inputs-with-mentions)
(reg-root-key-sub :chats-home-list :chats-home-list) (reg-root-key-sub :chats-home-list :chats-home-list)
;;lightbox
(reg-root-key-sub :lightbox/exit-signal :lightbox/exit-signal) (reg-root-key-sub :lightbox/exit-signal :lightbox/exit-signal)
(reg-root-key-sub :lightbox/zoom-out-signal :lightbox/zoom-out-signal) (reg-root-key-sub :lightbox/zoom-out-signal :lightbox/zoom-out-signal)
(reg-root-key-sub :lightbox/orientation :lightbox/orientation) (reg-root-key-sub :lightbox/orientation :lightbox/orientation)
(reg-root-key-sub :lightbox/scale :lightbox/scale)
;;messages ;;messages
(reg-root-key-sub :messages/messages :messages) (reg-root-key-sub :messages/messages :messages)