diff --git a/src/js/worklets/lightbox.js b/src/js/worklets/lightbox.js index 45acd84f9a..101eaa62b4 100644 --- a/src/js/worklets/lightbox.js +++ b/src/js/worklets/lightbox.js @@ -6,10 +6,3 @@ export function infoLayout(input, isTop) { return isTop ? input.value : -input.value; }); } - -export function textSheet(input, isHeight) { - return useDerivedValue(function () { - 'worklet'; - return isHeight ? input.value : -input.value; - }); -} diff --git a/src/status_im2/contexts/chat/lightbox/bottom_view.cljs b/src/status_im2/contexts/chat/lightbox/bottom_view.cljs index c9f49f2f08..1170449975 100644 --- a/src/status_im2/contexts/chat/lightbox/bottom_view.cljs +++ b/src/status_im2/contexts/chat/lightbox/bottom_view.cljs @@ -49,15 +49,15 @@ [item index _ render-data] [:f> f-small-image item index _ render-data]) - (defn bottom-view - [messages index scroll-index insets animations derived item-width props state] + [messages index scroll-index insets animations derived item-width props state transparent?] (let [padding-horizontal (- (/ item-width 2) (/ c/focused-image-size 2))] [reanimated/linear-gradient - {:colors [colors/neutral-100-opa-100 colors/neutral-100-opa-50] - :start {:x 0 :y 1} - :end {:x 0 :y 0} - :style (style/gradient-container insets animations derived)} + {:colors [colors/neutral-100-opa-100 colors/neutral-100-opa-80 colors/neutral-100-opa-0] + :location [0.2 0.9] + :start {:x 0 :y 1} + :end {:x 0 :y 0} + :style (style/gradient-container insets animations derived transparent?)} [text-sheet/view messages animations state props] [rn/flat-list {:ref #(reset! (:small-list-ref props) %) diff --git a/src/status_im2/contexts/chat/lightbox/style.cljs b/src/status_im2/contexts/chat/lightbox/style.cljs index a44d43d503..f9af3ee7e4 100644 --- a/src/status_im2/contexts/chat/lightbox/style.cljs +++ b/src/status_im2/contexts/chat/lightbox/style.cljs @@ -62,14 +62,16 @@ ;;;; BOTTOM-VIEW (defn gradient-container - [insets {:keys [opacity]} {:keys [bottom-layout]}] + [insets {:keys [opacity]} {:keys [bottom-layout]} transparent?] (reanimated/apply-animations-to-style {:transform [{:translateY bottom-layout}] :opacity opacity} {:position :absolute :overflow :visible + :display (if @transparent? :none :flex) :bottom 0 :padding-bottom (:bottom insets) + :padding-top c/text-min-height :z-index 3})) (defn content-container @@ -80,17 +82,27 @@ :justify-content :center}) -(defn background +(defn background-bottom-gradient [{:keys [overlay-opacity]} z-index] (reanimated/apply-animations-to-style - {:opacity overlay-opacity} - {:background-color colors/neutral-100-opa-70 - :position :absolute - :top 0 - :bottom 0 - :z-index z-index - :left 0 - :right 0})) + {:opacity (reanimated/interpolate overlay-opacity [0 0.1 0.4 1] [0 0.1 1 1])} + {:position :absolute + :top 0 + :bottom 0 + :z-index z-index + :left 0 + :right 0})) + +(defn background-top-gradient + [{:keys [overlay-opacity]} z-index] + (reanimated/apply-animations-to-style + {:opacity (reanimated/interpolate overlay-opacity [0.3 1] [0 1])} + {:position :absolute + :top 0 + :bottom 0 + :z-index z-index + :left 0 + :right 0})) (defn bottom-inset-cover-up [insets] diff --git a/src/status_im2/contexts/chat/lightbox/text_sheet/style.cljs b/src/status_im2/contexts/chat/lightbox/text_sheet/style.cljs index 17f7c7126c..71cbde2209 100644 --- a/src/status_im2/contexts/chat/lightbox/text_sheet/style.cljs +++ b/src/status_im2/contexts/chat/lightbox/text_sheet/style.cljs @@ -13,10 +13,11 @@ :left 0 :right 0})) -(def text-style +(defn text-style + [expanding-message?] {:color colors/white :margin-horizontal 20 - :margin-bottom constants/text-margin + :align-items (when-not expanding-message? :center) :flex-grow 1}) (def bar-container @@ -31,30 +32,37 @@ {:width 32 :height 4 :border-radius 100 - :background-color colors/white-opa-40 - :border-width 0.5 - :border-color colors/neutral-100}) + :background-color colors/white-opa-10}) (defn top-gradient - [{:keys [gradient-opacity]} insets] - (reanimated/apply-animations-to-style - {:opacity gradient-opacity} - {:position :absolute - :left 0 - :right 0 - :top (- (+ (:top insets) - constants/top-view-height)) - :height (+ (:top insets) - constants/top-view-height - constants/bar-container-height - constants/text-margin - (* constants/line-height 2)) - :z-index 1})) + [{:keys [derived-value]} {:keys [top]} insets max-height] + (let [initial-top (- (+ (:top insets) + constants/top-view-height)) + height (+ (:top insets) + constants/top-view-height + (* constants/line-height 2))] + (reanimated/apply-animations-to-style + {:opacity (reanimated/interpolate derived-value + [max-height (+ max-height constants/line-height)] + [0 1]) + :top (reanimated/interpolate top + [0 (- max-height)] + [initial-top (- initial-top max-height)] + {:extrapolateLeft "clamp" + :extrapolateRight "clamp"})} + {:position :absolute + :left 0 + :height height + :right 0 + :z-index 1}))) -(def bottom-gradient - {:position :absolute - :left 0 - :right 0 - :height 28 - :bottom 0 - :z-index 1}) +(defn bottom-gradient + [bottom-inset] + (let [gradient-distance (+ constants/small-list-height bottom-inset)] + {:position :absolute + :left 0 + :right 0 + :height (+ gradient-distance (* 2 constants/line-height)) + :bottom (- gradient-distance) + :opacity 0.8 + :z-index 1})) 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 b6ee8ed63f..095e66fead 100644 --- a/src/status_im2/contexts/chat/lightbox/text_sheet/utils.cljs +++ b/src/status_im2/contexts/chat/lightbox/text_sheet/utils.cljs @@ -3,41 +3,69 @@ [oops.core :as oops] [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.lightbox.constants :as constants] - [utils.worklets.lightbox :as worklet])) + [reagent.core :as r] + [status-im2.contexts.chat.lightbox.constants :as constants])) + +(defn- collapse-sheet + [{:keys [derived-value overlay-opacity saved-top expanded? overlay-z-index]}] + (reanimated/animate derived-value constants/text-min-height) + (reanimated/animate overlay-opacity 0) + (reanimated/set-shared-value saved-top (- constants/text-min-height)) + (reset! expanded? false) + (js/setTimeout #(reset! overlay-z-index 0) 300)) (defn sheet-gesture [{:keys [derived-value saved-top overlay-opacity gradient-opacity]} - expanded-height max-height overlay-z-index expanded? dragging?] - (-> (gesture/gesture-pan) - (gesture/on-start (fn [] - (reset! overlay-z-index 1) - (reset! dragging? true) - (reanimated/animate gradient-opacity 0))) - (gesture/on-update - (fn [e] - (let [new-value (+ (reanimated/get-shared-value saved-top) (oops/oget e "translationY")) - bounded-value (max (min (- new-value) expanded-height) constants/text-min-height) - progress (/ (- new-value) max-height)] - (reanimated/set-shared-value overlay-opacity progress) - (reanimated/set-shared-value derived-value bounded-value)))) - (gesture/on-end - (fn [] - (if (or (> (- (reanimated/get-shared-value derived-value)) - (reanimated/get-shared-value saved-top)) - (= (reanimated/get-shared-value derived-value) - constants/text-min-height)) - (do ; minimize - (reanimated/animate derived-value constants/text-min-height) - (reanimated/animate overlay-opacity 0) - (reanimated/set-shared-value saved-top (- constants/text-min-height)) - (reset! expanded? false) - (js/setTimeout #(reset! overlay-z-index 0) 300)) - (reanimated/set-shared-value saved-top - (- (reanimated/get-shared-value derived-value)))) - (when (= (reanimated/get-shared-value derived-value) expanded-height) - (reset! expanded? true)) - (reset! dragging? false))))) + expanded-height max-height full-height overlay-z-index expanded? dragging? expanding-message?] + (let [disable-gesture-update (r/atom false)] + (-> (gesture/gesture-pan) + (gesture/enabled expanding-message?) + (gesture/on-start (fn [] + (reset! overlay-z-index 1) + (reset! dragging? true) + (reset! disable-gesture-update false) + (when (not expanded?) + (reanimated/animate gradient-opacity 0)))) + (gesture/on-update + (fn [e] + (when-not @disable-gesture-update + (let [event-value (oops/oget e :translationY) + old-value (reanimated/get-shared-value saved-top) + new-value (+ old-value event-value) + progress (/ (- new-value) max-height) + reached-expanded? (< new-value (- max-height)) + upper-boundary? (< new-value (- full-height)) + lower-boundary? (and (> new-value (- constants/text-min-height)) + (pos? event-value))] + (when (and (not upper-boundary?) (not lower-boundary?)) + (reset! expanded? false) + (reanimated/set-shared-value overlay-opacity progress) + (reanimated/set-shared-value derived-value (- new-value))) + (when reached-expanded? (reset! expanded? true)) + (when lower-boundary? + (reset! disable-gesture-update true) + (collapse-sheet {:derived-value derived-value + :overlay-opacity overlay-opacity + :saved-top saved-top + :expanded? expanded? + :overlay-z-index overlay-z-index})))))) + (gesture/on-end + (fn [] + (let [shared-derived-value (reanimated/get-shared-value derived-value) + below-max-height? (< shared-derived-value max-height) + below-saved-top? (> (- shared-derived-value) + (reanimated/get-shared-value saved-top))] + (if (and below-max-height? below-saved-top?) + (collapse-sheet {:derived-value derived-value + :overlay-opacity overlay-opacity + :saved-top saved-top + :expanded? expanded? + :overlay-z-index overlay-z-index}) + (reanimated/set-shared-value saved-top + (- shared-derived-value))) + (when (= shared-derived-value expanded-height) + (reset! expanded? true)) + (reset! dragging? false))))))) (defn expand-sheet [{:keys [derived-value overlay-opacity saved-top]} @@ -49,12 +77,6 @@ (reset! overlay-z-index 1) (reset! expanded? true))) -(defn on-scroll - [e expanded? dragging? {:keys [gradient-opacity]}] - (if (and (> (oops/oget e "nativeEvent.contentOffset.y") 0) expanded? (not dragging?)) - (reanimated/animate gradient-opacity 1) - (reanimated/animate gradient-opacity 0))) - (defn on-layout [e text-height] (reset! text-height (oops/oget e "nativeEvent.layout.height"))) @@ -68,5 +90,5 @@ (defn init-derived-animations [{:keys [derived-value]}] - {:height (worklet/text-sheet derived-value true) - :top (worklet/text-sheet derived-value false)}) + {:height derived-value + :top (reanimated/interpolate derived-value [0 1] [0 -1])}) 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 c7662a0926..1831dd8627 100644 --- a/src/status_im2/contexts/chat/lightbox/text_sheet/view.cljs +++ b/src/status_im2/contexts/chat/lightbox/text_sheet/view.cljs @@ -13,13 +13,7 @@ [status-im2.contexts.chat.lightbox.text-sheet.utils :as utils] [status-im2.contexts.chat.messages.content.text.view :as message-view])) -(defn bar - [text-height] - (when (> text-height (* constants/line-height 2)) - [rn/view {:style style/bar-container} - [rn/view {:style style/bar}]])) - -(defn text-sheet +(defn- text-sheet [messages overlay-opacity overlay-z-index text-sheet-lock?] (let [text-height (reagent/atom 0) expanded? (reagent/atom false) @@ -33,50 +27,63 @@ constants/top-view-height (:bottom insets) (when platform/ios? (:top insets))) - expanded-height (min max-height - (+ constants/bar-container-height - @text-height - constants/text-margin)) + full-height (+ constants/bar-container-height + constants/text-margin + constants/line-height + @text-height) + expanded-height (min max-height full-height) animations (utils/init-animations overlay-opacity) - derived (utils/init-derived-animations animations)] - [gesture/gesture-detector - {:gesture (utils/sheet-gesture animations - expanded-height - max-height - overlay-z-index - expanded? - dragging?)} - [reanimated/touchable-opacity - {:active-opacity 1 - :on-press - #(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 - {:colors [colors/neutral-100-opa-0 colors/neutral-100] - :start {:x 0 :y 1} - :end {:x 0 :y 0} - :style (style/top-gradient animations insets)}] - [linear-gradient/linear-gradient - {:colors [colors/neutral-100-opa-50 colors/neutral-100-opa-0] - :start {:x 0 :y 1} - :end {:x 0 :y 0} - :style style/bottom-gradient}] - [gesture/scroll-view - {:scroll-enabled @expanded? - :scroll-event-throttle 16 - :on-scroll #(utils/on-scroll % @expanded? @dragging? animations) - :style {:height (- max-height constants/bar-container-height)}} - [message-view/render-parsed-text - {:content content - :chat-id chat-id - :style-override style/text-style - :on-layout #(utils/on-layout % text-height)}]]]])))) + derived (utils/init-derived-animations animations) + expanding-message? (> @text-height (* constants/line-height 2))] + [rn/view + [reanimated/linear-gradient + {:colors [colors/neutral-100-opa-0 colors/neutral-100] + :pointer-events :none + :locations [0 0.3] + :start {:x 0 :y 1} + :end {:x 0 :y 0} + :style (style/top-gradient animations derived insets max-height)}] + [gesture/gesture-detector + {:gesture (utils/sheet-gesture animations + expanded-height + max-height + full-height + overlay-z-index + expanded? + dragging? + expanding-message?)} + [gesture/gesture-detector + {:gesture (-> (gesture/gesture-tap) + (gesture/enabled (and expanding-message? (not @expanded?))) + (gesture/on-start (fn [] + (utils/expand-sheet animations + expanded-height + max-height + overlay-z-index + expanded? + text-sheet-lock?))))} + [reanimated/view {:style (style/sheet-container derived)} + (when expanding-message? + [rn/view {:style style/bar-container} + [rn/view {:style style/bar}]]) + [linear-gradient/linear-gradient + {:colors [colors/neutral-100-opa-100 colors/neutral-100-opa-70 colors/neutral-100-opa-0] + :start {:x 0 :y 1} + :end {:x 0 :y 0} + :locations [0.7 0.8 1] + :style (style/bottom-gradient (:bottom insets))}] + [gesture/scroll-view + {:scroll-enabled false + :scroll-event-throttle 16 + :bounces false + :style {:height (- max-height constants/bar-container-height)} + :content-container-style {:padding-top (when (not expanding-message?) + constants/bar-container-height)}} + [message-view/render-parsed-text + {:content content + :chat-id chat-id + :style-override (style/text-style expanding-message?) + :on-layout #(utils/on-layout % text-height)}]]]]]])))) (defn view [messages {:keys [overlay-opacity]} {:keys [overlay-z-index]} {:keys [text-sheet-lock?]}] diff --git a/src/status_im2/contexts/chat/lightbox/view.cljs b/src/status_im2/contexts/chat/lightbox/view.cljs index 49c69ad81b..048ba69f96 100644 --- a/src/status_im2/contexts/chat/lightbox/view.cljs +++ b/src/status_im2/contexts/chat/lightbox/view.cljs @@ -75,7 +75,16 @@ {:transform [{:translateY (:pan-y animations)} {:translateX (:pan-x animations)}]} {})} - [reanimated/view {:style (style/background animations @(:overlay-z-index state))}] + [reanimated/linear-gradient + {:colors [colors/neutral-100-opa-0 colors/neutral-100-opa-0 colors/neutral-100-opa-100 + colors/neutral-100] + :locations [0.3 0.4 0.6 1] + :pointer-events :none + :style (style/background-bottom-gradient animations @(:overlay-z-index state))}] + [reanimated/linear-gradient + {:colors [colors/neutral-100-opa-50 colors/neutral-100] + :pointer-events :none + :style (style/background-top-gradient animations @(:overlay-z-index state))}] [gesture/flat-list {:ref #(reset! (:flat-list-ref props) %) :key-fn :message-id @@ -85,6 +94,7 @@ :data @data :render-fn image :render-data {:opacity-value (:opacity animations) + :overlay-opacity (:overlay-opacity animations) :border-value (:border animations) :full-screen-scale (:full-screen-scale animations) :images-opacity (:images-opacity animations) @@ -105,9 +115,12 @@ :shows-vertical-scroll-indicator false :shows-horizontal-scroll-indicator false :on-viewable-items-changed handle-items-changed}]]] - (when (and (not @transparent?) (not landscape?)) + ;; NOTE: not un-mounting bottom-view based on `transparent?` (like we do with the top-view + ;; above), since we need to save the state of the text-sheet position. Instead, we use + ;; the `:display` style property to hide the bottom-sheet. + (when (not landscape?) [:f> bottom-view/bottom-view messages index scroll-index insets animations derived - item-width props state])])) + item-width props state transparent?])])) (defn- f-lightbox [] 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 77fb8a3955..ac2b8283f1 100644 --- a/src/status_im2/contexts/chat/lightbox/zoomable_image/utils.cljs +++ b/src/status_im2/contexts/chat/lightbox/zoomable_image/utils.cljs @@ -5,7 +5,6 @@ [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.animations :as anim] [status-im2.contexts.chat.lightbox.zoomable-image.constants :as constants] @@ -107,12 +106,36 @@ (rescale constants/min-scale true))) (defn toggle-opacity - [index {:keys [opacity-value border-value full-screen-scale transparent? props]} portrait?] + [index + {:keys [opacity-value overlay-opacity border-value full-screen-scale transparent? + props]} + portrait? last-overlay-opacity] (let [{:keys [small-list-ref timers text-sheet-lock?]} props - opacity (reanimated/get-shared-value opacity-value) scale-value (inc (/ constants/margin (:width (rn/get-window))))] - (if (= opacity 1) + (if @transparent? + (do + (js/clearTimeout (:hide-0 @timers)) + (js/clearTimeout (:hide-1 @timers)) + (reset! transparent? (not @transparent?)) + (swap! timers assoc + :show-0 + (js/setTimeout #((anim/animate opacity-value 1) + (anim/animate overlay-opacity @last-overlay-opacity) + (reset! last-overlay-opacity 0)) + 50)) + (swap! timers assoc + :show-1 + (js/setTimeout #(when @small-list-ref + (.scrollToIndex ^js @small-list-ref #js {:animated false :index index})) + 100)) + (anim/animate border-value 16) + (anim/animate full-screen-scale 1) + (when portrait? + (swap! timers assoc + :show-2 + (js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible true}}) + (if platform/ios? 150 50))))) (do (js/clearTimeout (:show-0 @timers)) (js/clearTimeout (:show-1 @timers)) @@ -122,26 +145,13 @@ (js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible false}}) (if platform/ios? 75 0))) (anim/animate opacity-value 0) + (reset! last-overlay-opacity (anim/get-val overlay-opacity)) + (anim/animate overlay-opacity 0) + (anim/animate border-value 0) + (anim/animate full-screen-scale scale-value) (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)) - (reset! transparent? (not @transparent?)) - (swap! timers assoc :show-0 (js/setTimeout #(anim/animate opacity-value 1) 50)) - (swap! timers assoc - :show-1 - (js/setTimeout #(when @small-list-ref - (.scrollToIndex ^js @small-list-ref #js {:animated false :index index})) - 100)) - (when portrait? - (swap! timers assoc - :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 full-screen-scale (if (= opacity 1) scale-value 1)))) + (js/setTimeout #(reset! text-sheet-lock? false) 300))))) ;;; Dimensions (defn get-dimensions 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 b5ea7204d4..d4269d09b3 100644 --- a/src/status_im2/contexts/chat/lightbox/zoomable_image/view.cljs +++ b/src/status_im2/contexts/chat/lightbox/zoomable_image/view.cljs @@ -6,6 +6,7 @@ [react-native.orientation :as orientation] [react-native.platform :as platform] [react-native.reanimated :as reanimated] + [reagent.core :as r] [status-im2.contexts.chat.lightbox.animations :as anim] [status-im2.contexts.chat.lightbox.zoomable-image.constants :as c] [status-im2.contexts.chat.lightbox.zoomable-image.style :as style] @@ -209,7 +210,8 @@ image-dimensions-nil?] (let [{:keys [transparent? set-full-height?]} render-data portrait? (= curr-orientation orientation/portrait) - on-tap #(utils/toggle-opacity index render-data portrait?) + last-overlay-opacity (r/atom 0) + on-tap #(utils/toggle-opacity index render-data portrait? last-overlay-opacity) 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) @@ -242,8 +244,8 @@ zoom-out-signal (rf/sub [:lightbox/zoom-out-signal]) {:keys [set-full-height? curr-orientation]} render-data focused? (= shared-element-id message-id) - ;; TODO - remove `image-dimensions` check, - ;; once https://github.com/status-im/status-desktop/issues/10944 is fixed + ;; TODO - remove `image-dimensions` check, once + ;; https://github.com/status-im/status-desktop/issues/10944 is fixed image-dimensions-nil? (not (and image-width image-height)) dimensions (utils/get-dimensions (or image-width (:screen-width render-data)) diff --git a/src/utils/worklets/lightbox.cljs b/src/utils/worklets/lightbox.cljs index 5804710cda..bec6e5ebec 100644 --- a/src/utils/worklets/lightbox.cljs +++ b/src/utils/worklets/lightbox.cljs @@ -5,7 +5,3 @@ (defn info-layout [input top?] (.infoLayout ^js layout-worklets input top?)) - -(defn text-sheet - [input height?] - (.textSheet ^js layout-worklets input height?))