Use compiler-safe Reanimated get/set APIs (#6391)
* Convert lightbox to get/set * Work around software-mansion/react-native-reanimated#6613 * Use get/set in more places * Port MainScrollProvider to get/set * Port more to get/set * Port composer to get/set * Remove unnecessary thread hops in composer * Port more things to get/set * Convert more to get/set, remove redundant runOnJS * Convert remaining cases to get/set
This commit is contained in:
@@ -1267,12 +1267,12 @@ function useScrollTracker({
|
||||
const contentHeight = useSharedValue(0)
|
||||
|
||||
const hasScrolledToTop = useDerivedValue(() =>
|
||||
withTiming(contentOffset.value === 0 ? 1 : 0),
|
||||
withTiming(contentOffset.get() === 0 ? 1 : 0),
|
||||
)
|
||||
|
||||
const hasScrolledToBottom = useDerivedValue(() =>
|
||||
withTiming(
|
||||
contentHeight.value - contentOffset.value - 5 <= scrollViewHeight.value
|
||||
contentHeight.get() - contentOffset.get() - 5 <= scrollViewHeight.get()
|
||||
? 1
|
||||
: 0,
|
||||
),
|
||||
@@ -1290,11 +1290,11 @@ function useScrollTracker({
|
||||
}) => {
|
||||
'worklet'
|
||||
if (typeof newContentHeight === 'number')
|
||||
contentHeight.value = Math.floor(newContentHeight)
|
||||
contentHeight.set(Math.floor(newContentHeight))
|
||||
if (typeof newContentOffset === 'number')
|
||||
contentOffset.value = Math.floor(newContentOffset)
|
||||
contentOffset.set(Math.floor(newContentOffset))
|
||||
if (typeof newScrollViewHeight === 'number')
|
||||
scrollViewHeight.value = Math.floor(newScrollViewHeight)
|
||||
scrollViewHeight.set(Math.floor(newScrollViewHeight))
|
||||
},
|
||||
[contentHeight, contentOffset, scrollViewHeight],
|
||||
)
|
||||
@@ -1310,21 +1310,22 @@ function useScrollTracker({
|
||||
},
|
||||
})
|
||||
|
||||
const onScrollViewContentSizeChange = useCallback(
|
||||
(_width: number, height: number) => {
|
||||
if (stickyBottom && height > contentHeight.value) {
|
||||
const onScrollViewContentSizeChangeUIThread = useCallback(
|
||||
(newContentHeight: number) => {
|
||||
'worklet'
|
||||
const oldContentHeight = contentHeight.get()
|
||||
let shouldScrollToBottom = false
|
||||
if (stickyBottom && newContentHeight > oldContentHeight) {
|
||||
const isFairlyCloseToBottom =
|
||||
contentHeight.value - contentOffset.value - 100 <=
|
||||
scrollViewHeight.value
|
||||
oldContentHeight - contentOffset.get() - 100 <= scrollViewHeight.get()
|
||||
if (isFairlyCloseToBottom) {
|
||||
runOnUI(() => {
|
||||
scrollTo(scrollViewRef, 0, contentHeight.value, true)
|
||||
})()
|
||||
shouldScrollToBottom = true
|
||||
}
|
||||
}
|
||||
showHideBottomBorder({
|
||||
newContentHeight: height,
|
||||
})
|
||||
showHideBottomBorder({newContentHeight})
|
||||
if (shouldScrollToBottom) {
|
||||
scrollTo(scrollViewRef, 0, newContentHeight, true)
|
||||
}
|
||||
},
|
||||
[
|
||||
showHideBottomBorder,
|
||||
@@ -1336,6 +1337,13 @@ function useScrollTracker({
|
||||
],
|
||||
)
|
||||
|
||||
const onScrollViewContentSizeChange = useCallback(
|
||||
(_width: number, height: number) => {
|
||||
runOnUI(onScrollViewContentSizeChangeUIThread)(height)
|
||||
},
|
||||
[onScrollViewContentSizeChangeUIThread],
|
||||
)
|
||||
|
||||
const onScrollViewLayout = useCallback(
|
||||
(evt: LayoutChangeEvent) => {
|
||||
showHideBottomBorder({
|
||||
@@ -1349,7 +1357,7 @@ function useScrollTracker({
|
||||
return {
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: interpolateColor(
|
||||
hasScrolledToTop.value,
|
||||
hasScrolledToTop.get(),
|
||||
[0, 1],
|
||||
[t.atoms.border_contrast_medium.borderColor, 'transparent'],
|
||||
),
|
||||
@@ -1359,7 +1367,7 @@ function useScrollTracker({
|
||||
return {
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: interpolateColor(
|
||||
hasScrolledToBottom.value,
|
||||
hasScrolledToBottom.get(),
|
||||
[0, 1],
|
||||
[t.atoms.border_contrast_medium.borderColor, 'transparent'],
|
||||
),
|
||||
@@ -1604,7 +1612,7 @@ function VideoUploadToolbar({state}: {state: VideoState}) {
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [{rotateZ: `${rotate.value}deg`}],
|
||||
transform: [{rotateZ: `${rotate.get()}deg`}],
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ function HomeHeaderLayoutDesktopAndTablet({
|
||||
{tabBarAnchor}
|
||||
<Animated.View
|
||||
onLayout={e => {
|
||||
headerHeight.value = e.nativeEvent.layout.height
|
||||
headerHeight.set(e.nativeEvent.layout.height)
|
||||
}}
|
||||
style={[
|
||||
t.atoms.bg,
|
||||
|
||||
@@ -43,7 +43,7 @@ export function HomeHeaderLayoutMobile({
|
||||
<Animated.View
|
||||
style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
|
||||
onLayout={e => {
|
||||
headerHeight.value = e.nativeEvent.layout.height
|
||||
headerHeight.set(e.nativeEvent.layout.height)
|
||||
}}>
|
||||
<View style={[pal.view, styles.topBar]}>
|
||||
<View style={[pal.view, {width: 100}]}>
|
||||
|
||||
@@ -87,11 +87,11 @@ const ImageItem = ({
|
||||
// Note: DO NOT move any logic reading animated values outside this function.
|
||||
useAnimatedReaction(
|
||||
() => {
|
||||
if (pinchScale.value !== 1) {
|
||||
if (pinchScale.get() !== 1) {
|
||||
// We're currently pinching.
|
||||
return true
|
||||
}
|
||||
const [, , committedScale] = readTransform(committedTransform.value)
|
||||
const [, , committedScale] = readTransform(committedTransform.get())
|
||||
if (committedScale !== 1) {
|
||||
// We started from a pinched in state.
|
||||
return true
|
||||
@@ -147,10 +147,10 @@ const ImageItem = ({
|
||||
.onStart(e => {
|
||||
'worklet'
|
||||
const screenSize = measureSafeArea()
|
||||
pinchOrigin.value = {
|
||||
pinchOrigin.set({
|
||||
x: e.focalX - screenSize.width / 2,
|
||||
y: e.focalY - screenSize.height / 2,
|
||||
}
|
||||
})
|
||||
})
|
||||
.onChange(e => {
|
||||
'worklet'
|
||||
@@ -160,7 +160,7 @@ const ImageItem = ({
|
||||
}
|
||||
// Don't let the picture zoom in so close that it gets blurry.
|
||||
// Also, like in stock Android apps, don't let the user zoom out further than 1:1.
|
||||
const [, , committedScale] = readTransform(committedTransform.value)
|
||||
const [, , committedScale] = readTransform(committedTransform.get())
|
||||
const maxCommittedScale = Math.max(
|
||||
MIN_SCREEN_ZOOM,
|
||||
(imageDimensions.width / screenSize.width) * MAX_ORIGINAL_IMAGE_ZOOM,
|
||||
@@ -171,20 +171,21 @@ const ImageItem = ({
|
||||
Math.max(minPinchScale, e.scale),
|
||||
maxPinchScale,
|
||||
)
|
||||
pinchScale.value = nextPinchScale
|
||||
pinchScale.set(nextPinchScale)
|
||||
|
||||
// Zooming out close to the corner could push us out of bounds, which we don't want on Android.
|
||||
// Calculate where we'll end up so we know how much to translate back to stay in bounds.
|
||||
const t = createTransform()
|
||||
prependPan(t, panTranslation.value)
|
||||
prependPinch(t, nextPinchScale, pinchOrigin.value, pinchTranslation.value)
|
||||
prependTransform(t, committedTransform.value)
|
||||
prependPan(t, panTranslation.get())
|
||||
prependPinch(t, nextPinchScale, pinchOrigin.get(), pinchTranslation.get())
|
||||
prependTransform(t, committedTransform.get())
|
||||
const [dx, dy] = getExtraTranslationToStayInBounds(t, screenSize)
|
||||
if (dx !== 0 || dy !== 0) {
|
||||
pinchTranslation.value = {
|
||||
x: pinchTranslation.value.x + dx,
|
||||
y: pinchTranslation.value.y + dy,
|
||||
}
|
||||
const pt = pinchTranslation.get()
|
||||
pinchTranslation.set({
|
||||
x: pt.x + dx,
|
||||
y: pt.y + dy,
|
||||
})
|
||||
}
|
||||
})
|
||||
.onEnd(() => {
|
||||
@@ -193,18 +194,18 @@ const ImageItem = ({
|
||||
let t = createTransform()
|
||||
prependPinch(
|
||||
t,
|
||||
pinchScale.value,
|
||||
pinchOrigin.value,
|
||||
pinchTranslation.value,
|
||||
pinchScale.get(),
|
||||
pinchOrigin.get(),
|
||||
pinchTranslation.get(),
|
||||
)
|
||||
prependTransform(t, committedTransform.value)
|
||||
prependTransform(t, committedTransform.get())
|
||||
applyRounding(t)
|
||||
committedTransform.value = t
|
||||
committedTransform.set(t)
|
||||
|
||||
// Reset just the pinch.
|
||||
pinchScale.value = 1
|
||||
pinchOrigin.value = {x: 0, y: 0}
|
||||
pinchTranslation.value = {x: 0, y: 0}
|
||||
pinchScale.set(1)
|
||||
pinchOrigin.set({x: 0, y: 0})
|
||||
pinchTranslation.set({x: 0, y: 0})
|
||||
})
|
||||
|
||||
const pan = Gesture.Pan()
|
||||
@@ -223,29 +224,29 @@ const ImageItem = ({
|
||||
prependPan(t, nextPanTranslation)
|
||||
prependPinch(
|
||||
t,
|
||||
pinchScale.value,
|
||||
pinchOrigin.value,
|
||||
pinchTranslation.value,
|
||||
pinchScale.get(),
|
||||
pinchOrigin.get(),
|
||||
pinchTranslation.get(),
|
||||
)
|
||||
prependTransform(t, committedTransform.value)
|
||||
prependTransform(t, committedTransform.get())
|
||||
|
||||
// Prevent panning from going out of bounds.
|
||||
const [dx, dy] = getExtraTranslationToStayInBounds(t, screenSize)
|
||||
nextPanTranslation.x += dx
|
||||
nextPanTranslation.y += dy
|
||||
panTranslation.value = nextPanTranslation
|
||||
panTranslation.set(nextPanTranslation)
|
||||
})
|
||||
.onEnd(() => {
|
||||
'worklet'
|
||||
// Commit just the pan.
|
||||
let t = createTransform()
|
||||
prependPan(t, panTranslation.value)
|
||||
prependTransform(t, committedTransform.value)
|
||||
prependPan(t, panTranslation.get())
|
||||
prependTransform(t, committedTransform.get())
|
||||
applyRounding(t)
|
||||
committedTransform.value = t
|
||||
committedTransform.set(t)
|
||||
|
||||
// Reset just the pan.
|
||||
panTranslation.value = {x: 0, y: 0}
|
||||
panTranslation.set({x: 0, y: 0})
|
||||
})
|
||||
|
||||
const singleTap = Gesture.Tap().onEnd(() => {
|
||||
@@ -261,11 +262,11 @@ const ImageItem = ({
|
||||
if (!imageDimensions || !imageAspect) {
|
||||
return
|
||||
}
|
||||
const [, , committedScale] = readTransform(committedTransform.value)
|
||||
const [, , committedScale] = readTransform(committedTransform.get())
|
||||
if (committedScale !== 1) {
|
||||
// Go back to 1:1 using the identity vector.
|
||||
let t = createTransform()
|
||||
committedTransform.value = withClampedSpring(t)
|
||||
committedTransform.set(withClampedSpring(t))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -299,7 +300,7 @@ const ImageItem = ({
|
||||
)
|
||||
const finalTransform = createTransform()
|
||||
prependPinch(finalTransform, scale, origin, {x: dx, y: dy})
|
||||
committedTransform.value = withClampedSpring(finalTransform)
|
||||
committedTransform.set(withClampedSpring(finalTransform))
|
||||
})
|
||||
|
||||
const composedGesture = isScrollViewBeingDragged
|
||||
@@ -313,13 +314,13 @@ const ImageItem = ({
|
||||
)
|
||||
|
||||
const containerStyle = useAnimatedStyle(() => {
|
||||
const {scaleAndMoveTransform, isHidden} = transforms.value
|
||||
const {scaleAndMoveTransform, isHidden} = transforms.get()
|
||||
// Apply the active adjustments on top of the committed transform before the gestures.
|
||||
// This is matrix multiplication, so operations are applied in the reverse order.
|
||||
let t = createTransform()
|
||||
prependPan(t, panTranslation.value)
|
||||
prependPinch(t, pinchScale.value, pinchOrigin.value, pinchTranslation.value)
|
||||
prependTransform(t, committedTransform.value)
|
||||
prependPan(t, panTranslation.get())
|
||||
prependPinch(t, pinchScale.get(), pinchOrigin.get(), pinchTranslation.get())
|
||||
prependTransform(t, committedTransform.get())
|
||||
const [translateX, translateY, scale] = readTransform(t)
|
||||
const manipulationTransform = [
|
||||
{translateX},
|
||||
@@ -338,7 +339,7 @@ const ImageItem = ({
|
||||
})
|
||||
|
||||
const imageCropStyle = useAnimatedStyle(() => {
|
||||
const {cropFrameTransform} = transforms.value
|
||||
const {cropFrameTransform} = transforms.get()
|
||||
return {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
@@ -347,7 +348,7 @@ const ImageItem = ({
|
||||
})
|
||||
|
||||
const imageStyle = useAnimatedStyle(() => {
|
||||
const {cropContentTransform} = transforms.value
|
||||
const {cropContentTransform} = transforms.get()
|
||||
return {
|
||||
flex: 1,
|
||||
transform: cropContentTransform,
|
||||
@@ -359,7 +360,7 @@ const ImageItem = ({
|
||||
const [hasLoaded, setHasLoaded] = useState(false)
|
||||
useAnimatedReaction(
|
||||
() => {
|
||||
return transforms.value.isResting && !hasLoaded
|
||||
return transforms.get().isResting && !hasLoaded
|
||||
},
|
||||
(show, prevShow) => {
|
||||
if (show && !prevShow) {
|
||||
|
||||
@@ -148,7 +148,7 @@ const ImageItem = ({
|
||||
)
|
||||
|
||||
const containerStyle = useAnimatedStyle(() => {
|
||||
const {scaleAndMoveTransform, isHidden} = transforms.value
|
||||
const {scaleAndMoveTransform, isHidden} = transforms.get()
|
||||
return {
|
||||
flex: 1,
|
||||
transform: scaleAndMoveTransform,
|
||||
@@ -158,7 +158,7 @@ const ImageItem = ({
|
||||
|
||||
const imageCropStyle = useAnimatedStyle(() => {
|
||||
const screenSize = measureSafeArea()
|
||||
const {cropFrameTransform} = transforms.value
|
||||
const {cropFrameTransform} = transforms.get()
|
||||
return {
|
||||
overflow: 'hidden',
|
||||
transform: cropFrameTransform,
|
||||
@@ -171,7 +171,7 @@ const ImageItem = ({
|
||||
})
|
||||
|
||||
const imageStyle = useAnimatedStyle(() => {
|
||||
const {cropContentTransform} = transforms.value
|
||||
const {cropContentTransform} = transforms.get()
|
||||
return {
|
||||
transform: cropContentTransform,
|
||||
width: '100%',
|
||||
@@ -184,7 +184,7 @@ const ImageItem = ({
|
||||
const [hasLoaded, setHasLoaded] = useState(false)
|
||||
useAnimatedReaction(
|
||||
() => {
|
||||
return transforms.value.isResting && !hasLoaded
|
||||
return transforms.get().isResting && !hasLoaded
|
||||
},
|
||||
(show, prevShow) => {
|
||||
if (show && !prevShow) {
|
||||
|
||||
@@ -109,18 +109,22 @@ export default function ImageViewRoot({
|
||||
|
||||
// https://github.com/software-mansion/react-native-reanimated/issues/6677
|
||||
requestAnimationFrame(() => {
|
||||
openProgress.value = canAnimate ? withClampedSpring(1, SLOW_SPRING) : 1
|
||||
openProgress.set(() =>
|
||||
canAnimate ? withClampedSpring(1, SLOW_SPRING) : 1,
|
||||
)
|
||||
})
|
||||
return () => {
|
||||
// https://github.com/software-mansion/react-native-reanimated/issues/6677
|
||||
requestAnimationFrame(() => {
|
||||
openProgress.value = canAnimate ? withClampedSpring(0, SLOW_SPRING) : 0
|
||||
openProgress.set(() =>
|
||||
canAnimate ? withClampedSpring(0, SLOW_SPRING) : 0,
|
||||
)
|
||||
})
|
||||
}
|
||||
}, [nextLightbox, openProgress])
|
||||
|
||||
useAnimatedReaction(
|
||||
() => openProgress.value === 0,
|
||||
() => openProgress.get() === 0,
|
||||
(isGone, wasGone) => {
|
||||
if (isGone && !wasGone) {
|
||||
runOnJS(setActiveLightbox)(null)
|
||||
@@ -130,7 +134,7 @@ export default function ImageViewRoot({
|
||||
|
||||
const onFlyAway = React.useCallback(() => {
|
||||
'worklet'
|
||||
openProgress.value = 0
|
||||
openProgress.set(0)
|
||||
runOnJS(onRequestClose)()
|
||||
}, [onRequestClose, openProgress])
|
||||
|
||||
@@ -187,7 +191,7 @@ function ImageView({
|
||||
const isFlyingAway = useSharedValue(false)
|
||||
|
||||
const containerStyle = useAnimatedStyle(() => {
|
||||
if (openProgress.value < 1 || isFlyingAway.value) {
|
||||
if (openProgress.get() < 1 || isFlyingAway.get()) {
|
||||
return {pointerEvents: 'none'}
|
||||
}
|
||||
return {pointerEvents: 'auto'}
|
||||
@@ -196,11 +200,12 @@ function ImageView({
|
||||
const backdropStyle = useAnimatedStyle(() => {
|
||||
const screenSize = measure(safeAreaRef)
|
||||
let opacity = 1
|
||||
if (openProgress.value < 1) {
|
||||
opacity = Math.sqrt(openProgress.value)
|
||||
const openProgressValue = openProgress.get()
|
||||
if (openProgressValue < 1) {
|
||||
opacity = Math.sqrt(openProgressValue)
|
||||
} else if (screenSize) {
|
||||
const dragProgress = Math.min(
|
||||
Math.abs(dismissSwipeTranslateY.value) / (screenSize.height / 2),
|
||||
Math.abs(dismissSwipeTranslateY.get()) / (screenSize.height / 2),
|
||||
1,
|
||||
)
|
||||
opacity -= dragProgress
|
||||
@@ -212,11 +217,11 @@ function ImageView({
|
||||
})
|
||||
|
||||
const animatedHeaderStyle = useAnimatedStyle(() => {
|
||||
const show = showControls && dismissSwipeTranslateY.value === 0
|
||||
const show = showControls && dismissSwipeTranslateY.get() === 0
|
||||
return {
|
||||
pointerEvents: show ? 'box-none' : 'none',
|
||||
opacity: withClampedSpring(
|
||||
show && openProgress.value === 1 ? 1 : 0,
|
||||
show && openProgress.get() === 1 ? 1 : 0,
|
||||
FAST_SPRING,
|
||||
),
|
||||
transform: [
|
||||
@@ -227,12 +232,12 @@ function ImageView({
|
||||
}
|
||||
})
|
||||
const animatedFooterStyle = useAnimatedStyle(() => {
|
||||
const show = showControls && dismissSwipeTranslateY.value === 0
|
||||
const show = showControls && dismissSwipeTranslateY.get() === 0
|
||||
return {
|
||||
flexGrow: 1,
|
||||
pointerEvents: show ? 'box-none' : 'none',
|
||||
opacity: withClampedSpring(
|
||||
show && openProgress.value === 1 ? 1 : 0,
|
||||
show && openProgress.get() === 1 ? 1 : 0,
|
||||
FAST_SPRING,
|
||||
),
|
||||
transform: [
|
||||
@@ -259,7 +264,7 @@ function ImageView({
|
||||
const screenSize = measure(safeAreaRef)
|
||||
return (
|
||||
!screenSize ||
|
||||
Math.abs(dismissSwipeTranslateY.value) > screenSize.height
|
||||
Math.abs(dismissSwipeTranslateY.get()) > screenSize.height
|
||||
)
|
||||
},
|
||||
(isOut, wasOut) => {
|
||||
@@ -397,10 +402,11 @@ function LightboxImage({
|
||||
const transforms = useDerivedValue(() => {
|
||||
'worklet'
|
||||
const safeArea = measureSafeArea()
|
||||
const openProgressValue = openProgress.get()
|
||||
const dismissTranslateY =
|
||||
isActive && openProgress.value === 1 ? dismissSwipeTranslateY.value : 0
|
||||
isActive && openProgressValue === 1 ? dismissSwipeTranslateY.get() : 0
|
||||
|
||||
if (openProgress.value === 0 && isFlyingAway.value) {
|
||||
if (openProgressValue === 0 && isFlyingAway.get()) {
|
||||
return {
|
||||
isHidden: true,
|
||||
isResting: false,
|
||||
@@ -410,9 +416,9 @@ function LightboxImage({
|
||||
}
|
||||
}
|
||||
|
||||
if (isActive && thumbRect && imageAspect && openProgress.value < 1) {
|
||||
if (isActive && thumbRect && imageAspect && openProgressValue < 1) {
|
||||
return interpolateTransform(
|
||||
openProgress.value,
|
||||
openProgressValue,
|
||||
thumbRect,
|
||||
safeArea,
|
||||
imageAspect,
|
||||
@@ -434,33 +440,37 @@ function LightboxImage({
|
||||
.maxPointers(1)
|
||||
.onUpdate(e => {
|
||||
'worklet'
|
||||
if (openProgress.value !== 1 || isFlyingAway.value) {
|
||||
if (openProgress.get() !== 1 || isFlyingAway.get()) {
|
||||
return
|
||||
}
|
||||
dismissSwipeTranslateY.value = e.translationY
|
||||
dismissSwipeTranslateY.set(e.translationY)
|
||||
})
|
||||
.onEnd(e => {
|
||||
'worklet'
|
||||
if (openProgress.value !== 1 || isFlyingAway.value) {
|
||||
if (openProgress.get() !== 1 || isFlyingAway.get()) {
|
||||
return
|
||||
}
|
||||
if (Math.abs(e.velocityY) > 200) {
|
||||
isFlyingAway.value = true
|
||||
if (dismissSwipeTranslateY.value === 0) {
|
||||
isFlyingAway.set(true)
|
||||
if (dismissSwipeTranslateY.get() === 0) {
|
||||
// HACK: If the initial value is 0, withDecay() animation doesn't start.
|
||||
// This is a bug in Reanimated, but for now we'll work around it like this.
|
||||
dismissSwipeTranslateY.value = 1
|
||||
dismissSwipeTranslateY.set(1)
|
||||
}
|
||||
dismissSwipeTranslateY.value = withDecay({
|
||||
velocity: e.velocityY,
|
||||
velocityFactor: Math.max(3500 / Math.abs(e.velocityY), 1), // Speed up if it's too slow.
|
||||
deceleration: 1, // Danger! This relies on the reaction below stopping it.
|
||||
})
|
||||
dismissSwipeTranslateY.set(() =>
|
||||
withDecay({
|
||||
velocity: e.velocityY,
|
||||
velocityFactor: Math.max(3500 / Math.abs(e.velocityY), 1), // Speed up if it's too slow.
|
||||
deceleration: 1, // Danger! This relies on the reaction below stopping it.
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
dismissSwipeTranslateY.value = withSpring(0, {
|
||||
stiffness: 700,
|
||||
damping: 50,
|
||||
})
|
||||
dismissSwipeTranslateY.set(() =>
|
||||
withSpring(0, {
|
||||
stiffness: 700,
|
||||
damping: 50,
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -131,11 +131,11 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
const lastForcedScrollY = useSharedValue(0)
|
||||
const adjustScrollForOtherPages = () => {
|
||||
'worklet'
|
||||
const currentScrollY = scrollY.value
|
||||
const currentScrollY = scrollY.get()
|
||||
const forcedScrollY = Math.min(currentScrollY, headerOnlyHeight)
|
||||
if (lastForcedScrollY.value !== forcedScrollY) {
|
||||
lastForcedScrollY.value = forcedScrollY
|
||||
const refs = scrollRefs.value
|
||||
if (lastForcedScrollY.get() !== forcedScrollY) {
|
||||
lastForcedScrollY.set(forcedScrollY)
|
||||
const refs = scrollRefs.get()
|
||||
for (let i = 0; i < refs.length; i++) {
|
||||
const scollRef = refs[i]
|
||||
if (i !== currentPage && scollRef != null) {
|
||||
@@ -167,7 +167,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
||||
const isPossiblyInvalid =
|
||||
headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight
|
||||
if (!isPossiblyInvalid) {
|
||||
scrollY.value = nextScrollY
|
||||
scrollY.set(nextScrollY)
|
||||
runOnJS(queueThrottledOnScroll)()
|
||||
}
|
||||
},
|
||||
@@ -246,7 +246,7 @@ let PagerTabBar = ({
|
||||
allowHeaderOverScroll?: boolean
|
||||
}): React.ReactNode => {
|
||||
const headerTransform = useAnimatedStyle(() => {
|
||||
const translateY = Math.min(scrollY.value, headerOnlyHeight) * -1
|
||||
const translateY = Math.min(scrollY.get(), headerOnlyHeight) * -1
|
||||
return {
|
||||
transform: [
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ export function createCustomBackdrop(
|
||||
// animated variables
|
||||
const opacity = useAnimatedStyle(() => ({
|
||||
opacity: interpolate(
|
||||
animatedIndex.value, // current snap index
|
||||
animatedIndex.get(), // current snap index
|
||||
[-1, 0], // input range
|
||||
[0, 0.5], // output range
|
||||
Extrapolation.CLAMP,
|
||||
|
||||
@@ -79,8 +79,8 @@ function ListImpl<ItemT>(
|
||||
onScrollFromContext?.(e, ctx)
|
||||
|
||||
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
|
||||
if (isScrolledDown.value !== didScrollDown) {
|
||||
isScrolledDown.value = didScrollDown
|
||||
if (isScrolledDown.get() !== didScrollDown) {
|
||||
isScrolledDown.set(didScrollDown)
|
||||
if (onScrolledDownChange != null) {
|
||||
runOnJS(handleScrolledDownChange)(didScrollDown)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
(v: boolean) => {
|
||||
'worklet'
|
||||
cancelAnimation(headerMode)
|
||||
headerMode.value = v ? V1.value : V0.value
|
||||
headerMode.set(v ? V1.get() : V0.get())
|
||||
},
|
||||
[headerMode],
|
||||
)
|
||||
@@ -52,9 +52,9 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
useEffect(() => {
|
||||
if (isWeb) {
|
||||
return listenToForcedWindowScroll(() => {
|
||||
startDragOffset.value = null
|
||||
startMode.value = null
|
||||
didJustRestoreScroll.value = true
|
||||
startDragOffset.set(null)
|
||||
startMode.set(null)
|
||||
didJustRestoreScroll.set(true)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -63,13 +63,14 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
(e: NativeScrollEvent) => {
|
||||
'worklet'
|
||||
if (isNative) {
|
||||
if (startDragOffset.value === null) {
|
||||
const startDragOffsetValue = startDragOffset.get()
|
||||
if (startDragOffsetValue === null) {
|
||||
return
|
||||
}
|
||||
const didScrollDown = e.contentOffset.y > startDragOffset.value
|
||||
startDragOffset.value = null
|
||||
startMode.value = null
|
||||
if (e.contentOffset.y < headerHeight.value) {
|
||||
const didScrollDown = e.contentOffset.y > startDragOffsetValue
|
||||
startDragOffset.set(null)
|
||||
startMode.set(null)
|
||||
if (e.contentOffset.y < headerHeight.get()) {
|
||||
// If we're close to the top, show the shell.
|
||||
setMode(false)
|
||||
} else if (didScrollDown) {
|
||||
@@ -77,7 +78,7 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
setMode(true)
|
||||
} else {
|
||||
// Snap to whichever state is the closest.
|
||||
setMode(Math.round(headerMode.value) === 1)
|
||||
setMode(Math.round(headerMode.get()) === 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -88,8 +89,8 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
(e: NativeScrollEvent) => {
|
||||
'worklet'
|
||||
if (isNative) {
|
||||
startDragOffset.value = e.contentOffset.y
|
||||
startMode.value = headerMode.value
|
||||
startDragOffset.set(e.contentOffset.y)
|
||||
startMode.set(headerMode.get())
|
||||
}
|
||||
},
|
||||
[headerMode, startDragOffset, startMode],
|
||||
@@ -123,10 +124,12 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
(e: NativeScrollEvent) => {
|
||||
'worklet'
|
||||
if (isNative) {
|
||||
if (startDragOffset.value === null || startMode.value === null) {
|
||||
const startDragOffsetValue = startDragOffset.get()
|
||||
const startModeValue = startMode.get()
|
||||
if (startDragOffsetValue === null || startModeValue === null) {
|
||||
if (
|
||||
headerMode.value !== 0 &&
|
||||
e.contentOffset.y < headerHeight.value
|
||||
headerMode.get() !== 0 &&
|
||||
e.contentOffset.y < headerHeight.get()
|
||||
) {
|
||||
// If we're close enough to the top, always show the shell.
|
||||
// Even if we're not dragging.
|
||||
@@ -137,29 +140,29 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
||||
|
||||
// The "mode" value is always between 0 and 1.
|
||||
// Figure out how much to move it based on the current dragged distance.
|
||||
const dy = e.contentOffset.y - startDragOffset.value
|
||||
const dy = e.contentOffset.y - startDragOffsetValue
|
||||
const dProgress = interpolate(
|
||||
dy,
|
||||
[-headerHeight.value, headerHeight.value],
|
||||
[-headerHeight.get(), headerHeight.get()],
|
||||
[-1, 1],
|
||||
)
|
||||
const newValue = clamp(startMode.value + dProgress, 0, 1)
|
||||
if (newValue !== headerMode.value) {
|
||||
const newValue = clamp(startModeValue + dProgress, 0, 1)
|
||||
if (newValue !== headerMode.get()) {
|
||||
// Manually adjust the value. This won't be (and shouldn't be) animated.
|
||||
// Cancel any any existing animation
|
||||
cancelAnimation(headerMode)
|
||||
headerMode.value = newValue
|
||||
headerMode.set(newValue)
|
||||
}
|
||||
} else {
|
||||
if (didJustRestoreScroll.value) {
|
||||
didJustRestoreScroll.value = false
|
||||
if (didJustRestoreScroll.get()) {
|
||||
didJustRestoreScroll.set(false)
|
||||
// Don't hide/show navbar based on scroll restoratoin.
|
||||
return
|
||||
}
|
||||
// On the web, we don't try to follow the drag because we don't know when it ends.
|
||||
// Instead, show/hide immediately based on whether we're scrolling up or down.
|
||||
const dy = e.contentOffset.y - (startDragOffset.value ?? 0)
|
||||
startDragOffset.value = e.contentOffset.y
|
||||
const dy = e.contentOffset.y - (startDragOffset.get() ?? 0)
|
||||
startDragOffset.set(e.contentOffset.y)
|
||||
|
||||
if (dy < 0 || e.contentOffset.y < WEB_HIDE_SHELL_THRESHOLD) {
|
||||
setMode(false)
|
||||
|
||||
@@ -134,7 +134,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
||||
footerMinimalShellTransform,
|
||||
]}
|
||||
onLayout={e => {
|
||||
footerHeight.value = e.nativeEvent.layout.height
|
||||
footerHeight.set(e.nativeEvent.layout.height)
|
||||
}}>
|
||||
{hasSession ? (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user