Scroll page animations (#14695)

* feat: scroll page animations
This commit is contained in:
Omar Basem 2023-01-19 16:46:05 +04:00 committed by GitHub
parent e8e8547879
commit 6c14fd1cb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 159 additions and 105 deletions

View File

@ -5,36 +5,36 @@ import { useDerivedValue, interpolate } from 'react-native-reanimated';
export function applyAnimationsToStyle(animations, style) {
return function() {
'worklet'
var animatedStyle = {}
for (var key in animations) {
if (key == "transform") {
var transforms = animations[key];
var animatedTransforms = []
for (var transform of transforms) {
var transformKey = Object.keys(transform)[0];
animatedTransforms.push({
[transformKey]: transform[transformKey].value
})
}
animatedStyle[key] = animatedTransforms;
} else {
animatedStyle[key] = animations[key].value;
}
}
return Object.assign(animatedStyle, style);
};
};
export function interpolateValue(sharedValue, inputRange, outputRange) {
export function interpolateValue(sharedValue, inputRange, outputRange, extrapolation) {
return useDerivedValue(
function () {
'worklet'
return interpolate(sharedValue.value, inputRange, outputRange);
return interpolate(sharedValue.value, inputRange, outputRange, extrapolation);
}
);
}

View File

@ -1,6 +1,7 @@
(ns react-native.reanimated
(:require ["react-native" :as rn]
["react-native-linear-gradient" :default LinearGradient]
["@react-native-community/blur" :as blur]
["react-native-reanimated" :default reanimated :refer
(useSharedValue useAnimatedStyle
withTiming
@ -30,6 +31,7 @@
(def touchable-opacity (create-animated-component (.-TouchableOpacity ^js rn)))
(def linear-gradient (create-animated-component LinearGradient))
(def blur-view (create-animated-component (.-BlurView blur)))
;; Hooks
(def use-shared-value useSharedValue)
@ -67,11 +69,14 @@
(def worklet-factory (js/require "../src/js/worklet_factory.js"))
(defn interpolate
[shared-value input-range output-range]
(.interpolateValue ^js worklet-factory
shared-value
(clj->js input-range)
(clj->js output-range)))
([shared-value input-range output-range]
(interpolate shared-value input-range output-range nil))
([shared-value input-range output-range extrapolation]
(.interpolateValue ^js worklet-factory
shared-value
(clj->js input-range)
(clj->js output-range)
(clj->js extrapolation))))
;;;; Component Animations

View File

@ -1,29 +1,26 @@
(ns status-im2.common.scroll-page.style
(:require [quo2.foundations.colors :as colors]
[react-native.platform :as platform]))
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]))
(defn image-slider
[height]
[size]
{:top (if platform/ios? 0 -64)
;; -64 is needed on android as the scroll doesn't
;; bounce so this slider won't disapear otherwise
:height height
:height size
:width size
:z-index 4
:flex 1})
(defn blur-slider
[height]
{:blur-amount 32
:blur-type :xlight
:overlay-color (if platform/ios? colors/white-opa-70 :transparent)
:style {:z-index 5
:top (if platform/ios? 0 -64)
;; -64 is needed on android as the scroll doesn't
;; bounce so this slider won't disapear otherwise
:position :absolute
:height height
:width "100%"
:flex 1}})
[animation]
(reanimated/apply-animations-to-style
{:transform [{:translateY animation}]}
{:z-index 5
:top 0
:position :absolute
:height (if platform/ios? 100 124)
:width "100%"
:flex 1}))
(defn scroll-view-container
[border-radius]
@ -31,4 +28,43 @@
:top -48
:overflow :scroll
:border-radius border-radius
:height "100%"})
:height "100%"})
(defn sticky-header-title
[animation]
(reanimated/apply-animations-to-style
{:opacity animation}
{:position :absolute
:flex-direction :row
:left 64
:top 16}))
(def sticky-header-image
{:border-radius 12
:border-width 0
:border-color :transparent
:width 24
:height 24
:margin-right 8})
(defn display-picture-container
[animation]
(reanimated/apply-animations-to-style
{:transform [{:scale animation}]}
{:border-radius 40
:border-width 1
:border-color colors/white
:position :absolute
:top -40
:left 17
:padding 2
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}))
(def display-picture
{:border-radius 50
:border-width 0
:border-color :transparent
:width 80
:height 80})

View File

@ -3,11 +3,11 @@
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.blur :as blur]
[react-native.platform :as platform]
[reagent.core :as reagent]
[status-im2.common.scroll-page.style :as style]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[react-native.reanimated :as reanimated]))
(defn icon-color
[]
@ -15,24 +15,8 @@
colors/white-opa-40
colors/neutral-80-opa-40))
(defn get-platform-value [value] (if platform/ios? (+ value 44) value))
(def negative-scroll-position-0 (if platform/ios? -44 0))
(def scroll-position-0 (if platform/ios? 44 0))
(def scroll-position-1 (if platform/ios? 86 134))
(def scroll-position-2 (if platform/ios? -26 18))
(defn get-header-size
[scroll-height]
(if (<= scroll-height scroll-position-2)
0
(->>
(+ (get-platform-value -17) scroll-height)
(* (if platform/ios? 3 1))
(max 0)
(min (if platform/ios? 100 124)))))
(def max-image-size 80)
(def min-image-size 32)
(defn diff-with-max-min
[value maximum minimum]
@ -42,54 +26,97 @@
(max minimum)
(min maximum)))
(defn icon-top-fn
[scroll-height]
(if (<= scroll-height negative-scroll-position-0)
-40
(->> (+ scroll-position-0 scroll-height)
(* (if platform/ios? 3 1))
(+ -40)
(min 8))))
(defn scroll-page-header
[scroll-height name page-nav cover sticky-header]
[:f>
(fn []
(let [input-range (if platform/ios? [-47 10] [0 150])
output-range (if platform/ios? [-100 0] [-169 -45])
y (reanimated/use-shared-value @scroll-height)
translate-animation (reanimated/interpolate y
input-range
output-range
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})
opacity-animation (reanimated/use-shared-value 0)
threshold (if platform/ios? 30 170)]
(rn/use-effect
#(do
(reanimated/set-shared-value y @scroll-height)
(reanimated/set-shared-value opacity-animation
(reanimated/with-timing (if (>= @scroll-height threshold) 1 0)
(clj->js {:duration 300}))))
[@scroll-height])
[:<>
[reanimated/blur-view
{:blur-amount 32
:blur-type :xlight
:overlay-color (if platform/ios? colors/white-opa-70 :transparent)
:style (style/blur-slider translate-animation)}]
[rn/view
{:style {:z-index 6
:margin-top (if platform/ios? 44 0)}}
[reanimated/view
{:style (style/sticky-header-title opacity-animation)}
[rn/image
{:source cover
:style style/sticky-header-image}]
[quo/text
{:size :paragraph-1
:weight :semi-bold
:style {:line-height 21}}
name]]
[quo/page-nav
{:horizontal-description? true
:one-icon-align-left? true
:align-mid? false
:page-nav-color :transparent
:mid-section {:type :text-with-description
:main-text nil
:description-img nil}
:right-section-buttons (:right-section-buttons page-nav)
:left-section {:icon :i/close
:icon-background-color (icon-color)
:on-press #(rf/dispatch [:navigate-back])}}]
(when sticky-header [sticky-header @scroll-height])]]))])
(defn icon-size-fn
[scroll-height]
(->> (+ scroll-position-0 scroll-height)
(* (if platform/ios? 3 1))
(- max-image-size)
(max min-image-size)
(min max-image-size)))
(defn display-picture
[scroll-height cover]
[:f>
(fn []
(let [input-range (if platform/ios? [-67 10] [0 150])
y (reanimated/use-shared-value @scroll-height)
animation (reanimated/interpolate y
input-range
[1.2 0.5]
{:extrapolateLeft "clamp"
:extrapolateRight "clamp"})]
(rn/use-effect #(do
(reanimated/set-shared-value y @scroll-height)
js/undefined)
[@scroll-height])
[reanimated/view
{:style (style/display-picture-container animation)}
[rn/image
{:source cover
:style style/display-picture}]]))])
(defn scroll-page
[icon cover page-nav name]
[cover page-nav name]
(let [scroll-height (reagent/atom negative-scroll-position-0)]
(fn [sticky-header children]
[:<>
[:<>
[rn/image
{:source cover
:position :absolute
:style (style/image-slider (get-header-size @scroll-height))}]
[blur/view (style/blur-slider (get-header-size @scroll-height))]]
[rn/view {:style {:z-index 6 :margin-top (if platform/ios? 44 0)}}
[quo/page-nav
{:horizontal-description? true
:one-icon-align-left? true
:align-mid? false
:page-nav-color :transparent
:page-nav-background-uri ""
:mid-section {:type :text-with-description
:main-text (when (>= @scroll-height scroll-position-1) name)
:description-img (when (>= @scroll-height scroll-position-1) icon)}
:right-section-buttons (:right-section-buttons page-nav)
:left-section {:icon :i/close
:icon-background-color (icon-color)
:on-press #(rf/dispatch [:navigate-back])}}]
(when sticky-header [sticky-header @scroll-height])]
[scroll-page-header scroll-height name page-nav cover sticky-header]
[rn/scroll-view
{:style (style/scroll-view-container (diff-with-max-min @scroll-height 16 0))
{:style (style/scroll-view-container
(diff-with-max-min @scroll-height 16 0))
:shows-vertical-scroll-indicator false
:scroll-event-throttle 4
:on-scroll #(swap! scroll-height (fn [] (int (oops/oget % "nativeEvent.contentOffset.y"))))}
:scroll-event-throttle 16
:on-scroll (fn [event]
(reset! scroll-height (int
(oops/oget
event
"nativeEvent.contentOffset.y"))))}
[rn/view {:style {:height 151}}
[rn/image
{:source cover
@ -102,5 +129,5 @@
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[children @scroll-height icon-top-fn icon-size-fn]])]])))
[display-picture scroll-height cover]
[children]])]])))

View File

@ -226,22 +226,9 @@
channel-heights first-channel-height]
(let [pending? (pos? requested-to-join-at)
thumbnail-image (get-in images [:thumbnail])]
(fn [scroll-height icon-top icon-size]
(fn []
[rn/view
[rn/view {:padding-horizontal 20}
[rn/view
{:border-radius 40
:border-width 1
:border-color colors/white
:position :absolute
:top (icon-top scroll-height)
:left 17
:padding 2
:background-color (colors/theme-colors
colors/white
colors/neutral-90)}
[quo/community-icon community
(icon-size scroll-height)]]
(when (and (not joined)
(not pending?)
(= status :gated))
@ -324,7 +311,6 @@
(let [channel-heights (reagent/atom [])
first-channel-height (reagent/atom 0)
scroll-component (scroll-page/scroll-page
(fn [] [quo/community-icon community 24])
{:uri (get-in images [:large :uri])}
{:right-section-buttons [{:icon :i/search
:background-color (scroll-page/icon-color)}