fix: removed bounce from lightbox text-sheet (#17664)

fix: improving UI and gestures

fix: expanding on press now works reliably

fix: center text and disable gestures for short messages

feat: added styling for one-lined messages

fix: small opacity fix

ref: small condition change

fix: horizontal swiping being unreliable with vertical scrolling

ref: reusing shared value from lightbox animations

fix: text-sheet bottom gradient not visible on android

fix: addressed qa issues 1,2,4

fix: zoomable-image stuck if moved vertically when full screen

clean: removed unused var

chore: removed unused require

fix: don't allow returning text-sheet down and up in one move

fix: text-sheet bar styling was wrong

fix: adjusted gradient animation with designs

fix: text-sheet bar opacity
This commit is contained in:
Lungu Cristian 2023-11-01 13:20:10 +02:00 committed by GitHub
parent 233d1b3261
commit 716007dc3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 234 additions and 171 deletions

View File

@ -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;
});
}

View File

@ -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) %)

View File

@ -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]

View File

@ -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}))

View File

@ -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])})

View File

@ -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?]}]

View File

@ -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
[]

View File

@ -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

View File

@ -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))

View File

@ -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?))