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:
dan
2024-11-17 15:06:28 +00:00
committed by GitHub
parent d575a2fdaa
commit 474c4eff29
26 changed files with 352 additions and 305 deletions
+27 -19
View File
@@ -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`}],
}
})
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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) {
+42 -32
View File
@@ -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,
}),
)
}
})
+6 -6
View File
@@ -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,
+2 -2
View File
@@ -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)
}
+27 -24
View File
@@ -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)