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:
+40
-32
@@ -81,40 +81,40 @@ export function Splash(props: React.PropsWithChildren<Props>) {
|
|||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
scale: interpolate(intro.value, [0, 1], [0.8, 1], 'clamp'),
|
scale: interpolate(intro.get(), [0, 1], [0.8, 1], 'clamp'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scale: interpolate(
|
scale: interpolate(
|
||||||
outroLogo.value,
|
outroLogo.get(),
|
||||||
[0, 0.08, 1],
|
[0, 0.08, 1],
|
||||||
[1, 0.8, 500],
|
[1, 0.8, 500],
|
||||||
'clamp',
|
'clamp',
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'),
|
opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const bottomLogoAnimation = useAnimatedStyle(() => {
|
const bottomLogoAnimation = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'),
|
opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const reducedLogoAnimation = useAnimatedStyle(() => {
|
const reducedLogoAnimation = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
scale: interpolate(intro.value, [0, 1], [0.8, 1], 'clamp'),
|
scale: interpolate(intro.get(), [0, 1], [0.8, 1], 'clamp'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
opacity: interpolate(intro.value, [0, 1], [0, 1], 'clamp'),
|
opacity: interpolate(intro.get(), [0, 1], [0, 1], 'clamp'),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const logoWrapperAnimation = useAnimatedStyle(() => {
|
const logoWrapperAnimation = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
opacity: interpolate(
|
opacity: interpolate(
|
||||||
outroAppOpacity.value,
|
outroAppOpacity.get(),
|
||||||
[0, 0.1, 0.2, 1],
|
[0, 0.1, 0.2, 1],
|
||||||
[1, 1, 0, 0],
|
[1, 1, 0, 0],
|
||||||
'clamp',
|
'clamp',
|
||||||
@@ -126,11 +126,11 @@ export function Splash(props: React.PropsWithChildren<Props>) {
|
|||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
scale: interpolate(outroApp.value, [0, 1], [1.1, 1], 'clamp'),
|
scale: interpolate(outroApp.get(), [0, 1], [1.1, 1], 'clamp'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
opacity: interpolate(
|
opacity: interpolate(
|
||||||
outroAppOpacity.value,
|
outroAppOpacity.get(),
|
||||||
[0, 0.1, 0.2, 1],
|
[0, 0.1, 0.2, 1],
|
||||||
[0, 0, 1, 1],
|
[0, 0, 1, 1],
|
||||||
'clamp',
|
'clamp',
|
||||||
@@ -146,29 +146,37 @@ export function Splash(props: React.PropsWithChildren<Props>) {
|
|||||||
if (isReady) {
|
if (isReady) {
|
||||||
SplashScreen.hideAsync()
|
SplashScreen.hideAsync()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
intro.value = withTiming(
|
intro.set(() =>
|
||||||
1,
|
withTiming(
|
||||||
{duration: 400, easing: Easing.out(Easing.cubic)},
|
1,
|
||||||
async () => {
|
{duration: 400, easing: Easing.out(Easing.cubic)},
|
||||||
// set these values to check animation at specific point
|
async () => {
|
||||||
// outroLogo.value = 0.1
|
// set these values to check animation at specific point
|
||||||
// outroApp.value = 0.1
|
// outroLogo.set(0.1)
|
||||||
outroLogo.value = withTiming(
|
// outroApp.set(0.1)
|
||||||
1,
|
outroLogo.set(() =>
|
||||||
{duration: 1200, easing: Easing.in(Easing.cubic)},
|
withTiming(
|
||||||
() => {
|
1,
|
||||||
runOnJS(onFinish)()
|
{duration: 1200, easing: Easing.in(Easing.cubic)},
|
||||||
},
|
() => {
|
||||||
)
|
runOnJS(onFinish)()
|
||||||
outroApp.value = withTiming(1, {
|
},
|
||||||
duration: 1200,
|
),
|
||||||
easing: Easing.inOut(Easing.cubic),
|
)
|
||||||
})
|
outroApp.set(() =>
|
||||||
outroAppOpacity.value = withTiming(1, {
|
withTiming(1, {
|
||||||
duration: 1200,
|
duration: 1200,
|
||||||
easing: Easing.in(Easing.cubic),
|
easing: Easing.inOut(Easing.cubic),
|
||||||
})
|
}),
|
||||||
},
|
)
|
||||||
|
outroAppOpacity.set(() =>
|
||||||
|
withTiming(1, {
|
||||||
|
duration: 1200,
|
||||||
|
easing: Easing.in(Easing.cubic),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ export function Loader(props: Props) {
|
|||||||
const rotation = useSharedValue(0)
|
const rotation = useSharedValue(0)
|
||||||
|
|
||||||
const animatedStyles = useAnimatedStyle(() => ({
|
const animatedStyles = useAnimatedStyle(() => ({
|
||||||
transform: [{rotate: rotation.value + 'deg'}],
|
transform: [{rotate: rotation.get() + 'deg'}],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
rotation.value = withRepeat(
|
rotation.set(() =>
|
||||||
withTiming(360, {duration: 500, easing: Easing.linear}),
|
withRepeat(withTiming(360, {duration: 500, easing: Easing.linear}), -1),
|
||||||
-1,
|
|
||||||
)
|
)
|
||||||
}, [rotation])
|
}, [rotation])
|
||||||
|
|
||||||
|
|||||||
@@ -55,13 +55,15 @@ export const ProgressGuideToast = React.forwardRef<
|
|||||||
|
|
||||||
// animate the opacity then set isOpen to false when done
|
// animate the opacity then set isOpen to false when done
|
||||||
const setIsntOpen = () => setIsOpen(false)
|
const setIsntOpen = () => setIsOpen(false)
|
||||||
opacity.value = withTiming(
|
opacity.set(() =>
|
||||||
0,
|
withTiming(
|
||||||
{
|
0,
|
||||||
duration: 400,
|
{
|
||||||
easing: Easing.out(Easing.cubic),
|
duration: 400,
|
||||||
},
|
easing: Easing.out(Easing.cubic),
|
||||||
() => runOnJS(setIsntOpen)(),
|
},
|
||||||
|
() => runOnJS(setIsntOpen)(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}, [setIsOpen, opacity])
|
}, [setIsOpen, opacity])
|
||||||
|
|
||||||
@@ -71,20 +73,24 @@ export const ProgressGuideToast = React.forwardRef<
|
|||||||
|
|
||||||
// animate the vertical translation, the opacity, and the checkmark
|
// animate the vertical translation, the opacity, and the checkmark
|
||||||
const playCheckmark = () => animatedCheckRef.current?.play()
|
const playCheckmark = () => animatedCheckRef.current?.play()
|
||||||
opacity.value = 0
|
opacity.set(0)
|
||||||
opacity.value = withTiming(
|
opacity.set(() =>
|
||||||
1,
|
withTiming(
|
||||||
{
|
1,
|
||||||
duration: 100,
|
{
|
||||||
easing: Easing.out(Easing.cubic),
|
duration: 100,
|
||||||
},
|
easing: Easing.out(Easing.cubic),
|
||||||
() => runOnJS(playCheckmark)(),
|
},
|
||||||
|
() => runOnJS(playCheckmark)(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
translateY.set(0)
|
||||||
|
translateY.set(() =>
|
||||||
|
withTiming(insets.top + 10, {
|
||||||
|
duration: 500,
|
||||||
|
easing: Easing.out(Easing.cubic),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
translateY.value = 0
|
|
||||||
translateY.value = withTiming(insets.top + 10, {
|
|
||||||
duration: 500,
|
|
||||||
easing: Easing.out(Easing.cubic),
|
|
||||||
})
|
|
||||||
|
|
||||||
// start the countdown timer to autoclose
|
// start the countdown timer to autoclose
|
||||||
timeoutRef.current = setTimeout(close, visibleDuration || 5e3)
|
timeoutRef.current = setTimeout(close, visibleDuration || 5e3)
|
||||||
@@ -114,8 +120,8 @@ export const ProgressGuideToast = React.forwardRef<
|
|||||||
}, [winDim.width])
|
}, [winDim.width])
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
transform: [{translateY: translateY.value}],
|
transform: [{translateY: translateY.get()}],
|
||||||
opacity: opacity.value,
|
opacity: opacity.get(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -32,21 +32,25 @@ export const AnimatedCheck = React.forwardRef<
|
|||||||
const checkAnim = useSharedValue(0)
|
const checkAnim = useSharedValue(0)
|
||||||
|
|
||||||
const circleAnimatedProps = useAnimatedProps(() => ({
|
const circleAnimatedProps = useAnimatedProps(() => ({
|
||||||
strokeDashoffset: 166 - circleAnim.value * 166,
|
strokeDashoffset: 166 - circleAnim.get() * 166,
|
||||||
}))
|
}))
|
||||||
const checkAnimatedProps = useAnimatedProps(() => ({
|
const checkAnimatedProps = useAnimatedProps(() => ({
|
||||||
strokeDashoffset: 48 - 48 * checkAnim.value,
|
strokeDashoffset: 48 - 48 * checkAnim.get(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const play = React.useCallback(
|
const play = React.useCallback(
|
||||||
(cb?: () => void) => {
|
(cb?: () => void) => {
|
||||||
circleAnim.value = 0
|
circleAnim.set(0)
|
||||||
checkAnim.value = 0
|
checkAnim.set(0)
|
||||||
|
|
||||||
circleAnim.value = withTiming(1, {duration: 500, easing: Easing.linear})
|
circleAnim.set(() =>
|
||||||
checkAnim.value = withDelay(
|
withTiming(1, {duration: 500, easing: Easing.linear}),
|
||||||
500,
|
)
|
||||||
withTiming(1, {duration: 300, easing: Easing.linear}, cb),
|
checkAnim.set(() =>
|
||||||
|
withDelay(
|
||||||
|
500,
|
||||||
|
withTiming(1, {duration: 300, easing: Easing.linear}, cb),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[circleAnim, checkAnim],
|
[circleAnim, checkAnim],
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function ActionsWrapper({
|
|||||||
const scale = useSharedValue(1)
|
const scale = useSharedValue(1)
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
transform: [{scale: scale.value}],
|
transform: [{scale: scale.get()}],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const open = React.useCallback(() => {
|
const open = React.useCallback(() => {
|
||||||
@@ -46,7 +46,7 @@ export function ActionsWrapper({
|
|||||||
const shrink = React.useCallback(() => {
|
const shrink = React.useCallback(() => {
|
||||||
'worklet'
|
'worklet'
|
||||||
cancelAnimation(scale)
|
cancelAnimation(scale)
|
||||||
scale.value = withTiming(1, {duration: 200})
|
scale.set(() => withTiming(1, {duration: 200}))
|
||||||
}, [scale])
|
}, [scale])
|
||||||
|
|
||||||
const doubleTapGesture = Gesture.Tap()
|
const doubleTapGesture = Gesture.Tap()
|
||||||
@@ -58,11 +58,13 @@ export function ActionsWrapper({
|
|||||||
const pressAndHoldGesture = Gesture.LongPress()
|
const pressAndHoldGesture = Gesture.LongPress()
|
||||||
.onStart(() => {
|
.onStart(() => {
|
||||||
'worklet'
|
'worklet'
|
||||||
scale.value = withTiming(1.05, {duration: 200}, finished => {
|
scale.set(() =>
|
||||||
if (!finished) return
|
withTiming(1.05, {duration: 200}, finished => {
|
||||||
runOnJS(open)()
|
if (!finished) return
|
||||||
shrink()
|
runOnJS(open)()
|
||||||
})
|
shrink()
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.onTouchesUp(shrink)
|
.onTouchesUp(shrink)
|
||||||
.onTouchesMove(shrink)
|
.onTouchesMove(shrink)
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ export function ChatEmptyPill() {
|
|||||||
|
|
||||||
const onPressIn = React.useCallback(() => {
|
const onPressIn = React.useCallback(() => {
|
||||||
if (isWeb) return
|
if (isWeb) return
|
||||||
scale.value = withTiming(1.075, {duration: 100})
|
scale.set(() => withTiming(1.075, {duration: 100}))
|
||||||
}, [scale])
|
}, [scale])
|
||||||
|
|
||||||
const onPressOut = React.useCallback(() => {
|
const onPressOut = React.useCallback(() => {
|
||||||
if (isWeb) return
|
if (isWeb) return
|
||||||
scale.value = withTiming(1, {duration: 100})
|
scale.set(() => withTiming(1, {duration: 100}))
|
||||||
}, [scale])
|
}, [scale])
|
||||||
|
|
||||||
const onPress = React.useCallback(() => {
|
const onPress = React.useCallback(() => {
|
||||||
@@ -61,7 +61,7 @@ export function ChatEmptyPill() {
|
|||||||
}, [playHaptic, prompts.length])
|
}, [playHaptic, prompts.length])
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
transform: [{scale: scale.value}],
|
transform: [{scale: scale.get()}],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ export function NewMessagesPill({
|
|||||||
|
|
||||||
const onPressIn = React.useCallback(() => {
|
const onPressIn = React.useCallback(() => {
|
||||||
if (isWeb) return
|
if (isWeb) return
|
||||||
scale.value = withTiming(1.075, {duration: 100})
|
scale.set(() => withTiming(1.075, {duration: 100}))
|
||||||
}, [scale])
|
}, [scale])
|
||||||
|
|
||||||
const onPressOut = React.useCallback(() => {
|
const onPressOut = React.useCallback(() => {
|
||||||
if (isWeb) return
|
if (isWeb) return
|
||||||
scale.value = withTiming(1, {duration: 100})
|
scale.set(() => withTiming(1, {duration: 100}))
|
||||||
}, [scale])
|
}, [scale])
|
||||||
|
|
||||||
const onPress = React.useCallback(() => {
|
const onPress = React.useCallback(() => {
|
||||||
@@ -49,7 +49,7 @@ export function NewMessagesPill({
|
|||||||
}, [onPressInner, playHaptic])
|
}, [onPressInner, playHaptic])
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
transform: [{scale: scale.value}],
|
transform: [{scale: scale.get()}],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function GestureActionView({
|
|||||||
const clampedTransX = useDerivedValue(() => {
|
const clampedTransX = useDerivedValue(() => {
|
||||||
const min = actions.leftFirst ? -MAX_WIDTH : 0
|
const min = actions.leftFirst ? -MAX_WIDTH : 0
|
||||||
const max = actions.rightFirst ? MAX_WIDTH : 0
|
const max = actions.rightFirst ? MAX_WIDTH : 0
|
||||||
return clamp(transX.value, min, max)
|
return clamp(transX.get(), min, max)
|
||||||
})
|
})
|
||||||
|
|
||||||
const iconScale = useSharedValue(1)
|
const iconScale = useSharedValue(1)
|
||||||
@@ -75,21 +75,23 @@ export function GestureActionView({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iconScale.value = withSequence(
|
iconScale.set(() =>
|
||||||
withTiming(1.2, {duration: 175}),
|
withSequence(
|
||||||
withTiming(1, {duration: 100}),
|
withTiming(1.2, {duration: 175}),
|
||||||
|
withTiming(1, {duration: 100}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
useAnimatedReaction(
|
useAnimatedReaction(
|
||||||
() => transX,
|
() => transX,
|
||||||
() => {
|
() => {
|
||||||
if (transX.value === 0) {
|
if (transX.get() === 0) {
|
||||||
runOnJS(setActiveAction)(null)
|
runOnJS(setActiveAction)(null)
|
||||||
} else if (transX.value < 0) {
|
} else if (transX.get() < 0) {
|
||||||
if (
|
if (
|
||||||
actions.leftSecond &&
|
actions.leftSecond &&
|
||||||
transX.value <= -actions.leftSecond.threshold
|
transX.get() <= -actions.leftSecond.threshold
|
||||||
) {
|
) {
|
||||||
if (activeAction !== 'leftSecond') {
|
if (activeAction !== 'leftSecond') {
|
||||||
runOnJS(setActiveAction)('leftSecond')
|
runOnJS(setActiveAction)('leftSecond')
|
||||||
@@ -97,10 +99,10 @@ export function GestureActionView({
|
|||||||
} else if (activeAction !== 'leftFirst') {
|
} else if (activeAction !== 'leftFirst') {
|
||||||
runOnJS(setActiveAction)('leftFirst')
|
runOnJS(setActiveAction)('leftFirst')
|
||||||
}
|
}
|
||||||
} else if (transX.value > 0) {
|
} else if (transX.get() > 0) {
|
||||||
if (
|
if (
|
||||||
actions.rightSecond &&
|
actions.rightSecond &&
|
||||||
transX.value > actions.rightSecond.threshold
|
transX.get() > actions.rightSecond.threshold
|
||||||
) {
|
) {
|
||||||
if (activeAction !== 'rightSecond') {
|
if (activeAction !== 'rightSecond') {
|
||||||
runOnJS(setActiveAction)('rightSecond')
|
runOnJS(setActiveAction)('rightSecond')
|
||||||
@@ -119,44 +121,44 @@ export function GestureActionView({
|
|||||||
.activeOffsetY([-200, 200])
|
.activeOffsetY([-200, 200])
|
||||||
.onStart(() => {
|
.onStart(() => {
|
||||||
'worklet'
|
'worklet'
|
||||||
isActive.value = true
|
isActive.set(true)
|
||||||
})
|
})
|
||||||
.onChange(e => {
|
.onChange(e => {
|
||||||
'worklet'
|
'worklet'
|
||||||
transX.value = e.translationX
|
transX.set(e.translationX)
|
||||||
|
|
||||||
if (e.translationX < 0) {
|
if (e.translationX < 0) {
|
||||||
// Left side
|
// Left side
|
||||||
if (actions.leftSecond) {
|
if (actions.leftSecond) {
|
||||||
if (
|
if (
|
||||||
e.translationX <= -actions.leftSecond.threshold &&
|
e.translationX <= -actions.leftSecond.threshold &&
|
||||||
!hitSecond.value
|
!hitSecond.get()
|
||||||
) {
|
) {
|
||||||
runPopAnimation()
|
runPopAnimation()
|
||||||
runOnJS(haptic)()
|
runOnJS(haptic)()
|
||||||
hitSecond.value = true
|
hitSecond.set(true)
|
||||||
} else if (
|
} else if (
|
||||||
hitSecond.value &&
|
hitSecond.get() &&
|
||||||
e.translationX > -actions.leftSecond.threshold
|
e.translationX > -actions.leftSecond.threshold
|
||||||
) {
|
) {
|
||||||
runPopAnimation()
|
runPopAnimation()
|
||||||
hitSecond.value = false
|
hitSecond.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hitSecond.value && actions.leftFirst) {
|
if (!hitSecond.get() && actions.leftFirst) {
|
||||||
if (
|
if (
|
||||||
e.translationX <= -actions.leftFirst.threshold &&
|
e.translationX <= -actions.leftFirst.threshold &&
|
||||||
!hitFirst.value
|
!hitFirst.get()
|
||||||
) {
|
) {
|
||||||
runPopAnimation()
|
runPopAnimation()
|
||||||
runOnJS(haptic)()
|
runOnJS(haptic)()
|
||||||
hitFirst.value = true
|
hitFirst.set(true)
|
||||||
} else if (
|
} else if (
|
||||||
hitFirst.value &&
|
hitFirst.get() &&
|
||||||
e.translationX > -actions.leftFirst.threshold
|
e.translationX > -actions.leftFirst.threshold
|
||||||
) {
|
) {
|
||||||
hitFirst.value = false
|
hitFirst.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (e.translationX > 0) {
|
} else if (e.translationX > 0) {
|
||||||
@@ -164,33 +166,33 @@ export function GestureActionView({
|
|||||||
if (actions.rightSecond) {
|
if (actions.rightSecond) {
|
||||||
if (
|
if (
|
||||||
e.translationX >= actions.rightSecond.threshold &&
|
e.translationX >= actions.rightSecond.threshold &&
|
||||||
!hitSecond.value
|
!hitSecond.get()
|
||||||
) {
|
) {
|
||||||
runPopAnimation()
|
runPopAnimation()
|
||||||
runOnJS(haptic)()
|
runOnJS(haptic)()
|
||||||
hitSecond.value = true
|
hitSecond.set(true)
|
||||||
} else if (
|
} else if (
|
||||||
hitSecond.value &&
|
hitSecond.get() &&
|
||||||
e.translationX < actions.rightSecond.threshold
|
e.translationX < actions.rightSecond.threshold
|
||||||
) {
|
) {
|
||||||
runPopAnimation()
|
runPopAnimation()
|
||||||
hitSecond.value = false
|
hitSecond.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hitSecond.value && actions.rightFirst) {
|
if (!hitSecond.get() && actions.rightFirst) {
|
||||||
if (
|
if (
|
||||||
e.translationX >= actions.rightFirst.threshold &&
|
e.translationX >= actions.rightFirst.threshold &&
|
||||||
!hitFirst.value
|
!hitFirst.get()
|
||||||
) {
|
) {
|
||||||
runPopAnimation()
|
runPopAnimation()
|
||||||
runOnJS(haptic)()
|
runOnJS(haptic)()
|
||||||
hitFirst.value = true
|
hitFirst.set(true)
|
||||||
} else if (
|
} else if (
|
||||||
hitFirst.value &&
|
hitFirst.get() &&
|
||||||
e.translationX < actions.rightFirst.threshold
|
e.translationX < actions.rightFirst.threshold
|
||||||
) {
|
) {
|
||||||
hitFirst.value = false
|
hitFirst.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,29 +200,29 @@ export function GestureActionView({
|
|||||||
.onEnd(e => {
|
.onEnd(e => {
|
||||||
'worklet'
|
'worklet'
|
||||||
if (e.translationX < 0) {
|
if (e.translationX < 0) {
|
||||||
if (hitSecond.value && actions.leftSecond) {
|
if (hitSecond.get() && actions.leftSecond) {
|
||||||
runOnJS(actions.leftSecond.action)()
|
runOnJS(actions.leftSecond.action)()
|
||||||
} else if (hitFirst.value && actions.leftFirst) {
|
} else if (hitFirst.get() && actions.leftFirst) {
|
||||||
runOnJS(actions.leftFirst.action)()
|
runOnJS(actions.leftFirst.action)()
|
||||||
}
|
}
|
||||||
} else if (e.translationX > 0) {
|
} else if (e.translationX > 0) {
|
||||||
if (hitSecond.value && actions.rightSecond) {
|
if (hitSecond.get() && actions.rightSecond) {
|
||||||
runOnJS(actions.rightSecond.action)()
|
runOnJS(actions.rightSecond.action)()
|
||||||
} else if (hitSecond.value && actions.rightFirst) {
|
} else if (hitSecond.get() && actions.rightFirst) {
|
||||||
runOnJS(actions.rightFirst.action)()
|
runOnJS(actions.rightFirst.action)()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transX.value = withTiming(0, {duration: 200})
|
transX.set(() => withTiming(0, {duration: 200}))
|
||||||
hitFirst.value = false
|
hitFirst.set(false)
|
||||||
hitSecond.value = false
|
hitSecond.set(false)
|
||||||
isActive.value = false
|
isActive.set(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
const composedGesture = Gesture.Simultaneous(panGesture)
|
const composedGesture = Gesture.Simultaneous(panGesture)
|
||||||
|
|
||||||
const animatedSliderStyle = useAnimatedStyle(() => {
|
const animatedSliderStyle = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
transform: [{translateX: clampedTransX.value}],
|
transform: [{translateX: clampedTransX.get()}],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -274,7 +276,7 @@ export function GestureActionView({
|
|||||||
const animatedBackgroundStyle = useAnimatedStyle(() => {
|
const animatedBackgroundStyle = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
backgroundColor: interpolateColor(
|
backgroundColor: interpolateColor(
|
||||||
clampedTransX.value,
|
clampedTransX.get(),
|
||||||
interpolation.inputRange,
|
interpolation.inputRange,
|
||||||
// @ts-expect-error - Weird type expected by reanimated, but this is okay
|
// @ts-expect-error - Weird type expected by reanimated, but this is okay
|
||||||
interpolation.outputRange,
|
interpolation.outputRange,
|
||||||
@@ -283,10 +285,10 @@ export function GestureActionView({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const animatedIconStyle = useAnimatedStyle(() => {
|
const animatedIconStyle = useAnimatedStyle(() => {
|
||||||
const absTransX = Math.abs(clampedTransX.value)
|
const absTransX = Math.abs(clampedTransX.get())
|
||||||
return {
|
return {
|
||||||
opacity: interpolate(absTransX, [0, 75], [0.15, 1]),
|
opacity: interpolate(absTransX, [0, 75], [0.15, 1]),
|
||||||
transform: [{scale: iconScale.value}],
|
transform: [{scale: iconScale.get()}],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React from 'react'
|
|||||||
import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native'
|
import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
cancelAnimation,
|
cancelAnimation,
|
||||||
runOnJS,
|
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
useReducedMotion,
|
useReducedMotion,
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
@@ -32,27 +31,25 @@ export function PressableScale({
|
|||||||
const scale = useSharedValue(1)
|
const scale = useSharedValue(1)
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
transform: [{scale: scale.value}],
|
transform: [{scale: scale.get()}],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPressable
|
<AnimatedPressable
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
onPressIn={e => {
|
onPressIn={e => {
|
||||||
'worklet'
|
|
||||||
if (onPressIn) {
|
if (onPressIn) {
|
||||||
runOnJS(onPressIn)(e)
|
onPressIn(e)
|
||||||
}
|
}
|
||||||
cancelAnimation(scale)
|
cancelAnimation(scale)
|
||||||
scale.value = withTiming(targetScale, {duration: 100})
|
scale.set(() => withTiming(targetScale, {duration: 100}))
|
||||||
}}
|
}}
|
||||||
onPressOut={e => {
|
onPressOut={e => {
|
||||||
'worklet'
|
|
||||||
if (onPressOut) {
|
if (onPressOut) {
|
||||||
runOnJS(onPressOut)(e)
|
onPressOut(e)
|
||||||
}
|
}
|
||||||
cancelAnimation(scale)
|
cancelAnimation(scale)
|
||||||
scale.value = withTiming(1, {duration: 100})
|
scale.set(() => withTiming(1, {duration: 100}))
|
||||||
}}
|
}}
|
||||||
style={[!reducedMotion && animatedStyle, style]}
|
style={[!reducedMotion && animatedStyle, style]}
|
||||||
{...rest}>
|
{...rest}>
|
||||||
|
|||||||
@@ -10,15 +10,16 @@ export function useMinimalShellHeaderTransform() {
|
|||||||
const {headerHeight} = useShellLayout()
|
const {headerHeight} = useShellLayout()
|
||||||
|
|
||||||
const headerTransform = useAnimatedStyle(() => {
|
const headerTransform = useAnimatedStyle(() => {
|
||||||
|
const headerModeValue = headerMode.get()
|
||||||
return {
|
return {
|
||||||
pointerEvents: headerMode.value === 0 ? 'auto' : 'none',
|
pointerEvents: headerModeValue === 0 ? 'auto' : 'none',
|
||||||
opacity: Math.pow(1 - headerMode.value, 2),
|
opacity: Math.pow(1 - headerModeValue, 2),
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: interpolate(
|
translateY: interpolate(
|
||||||
headerMode.value,
|
headerModeValue,
|
||||||
[0, 1],
|
[0, 1],
|
||||||
[0, -headerHeight.value],
|
[0, -headerHeight.get()],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -33,15 +34,16 @@ export function useMinimalShellFooterTransform() {
|
|||||||
const {footerHeight} = useShellLayout()
|
const {footerHeight} = useShellLayout()
|
||||||
|
|
||||||
const footerTransform = useAnimatedStyle(() => {
|
const footerTransform = useAnimatedStyle(() => {
|
||||||
|
const footerModeValue = footerMode.get()
|
||||||
return {
|
return {
|
||||||
pointerEvents: footerMode.value === 0 ? 'auto' : 'none',
|
pointerEvents: footerModeValue === 0 ? 'auto' : 'none',
|
||||||
opacity: Math.pow(1 - footerMode.value, 2),
|
opacity: Math.pow(1 - footerModeValue, 2),
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: interpolate(
|
translateY: interpolate(
|
||||||
footerMode.value,
|
footerModeValue,
|
||||||
[0, 1],
|
[0, 1],
|
||||||
[0, footerHeight.value],
|
[0, footerHeight.get()],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -58,7 +60,7 @@ export function useMinimalShellFabTransform() {
|
|||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: interpolate(footerMode.value, [0, 1], [-44, 0]),
|
translateY: interpolate(footerMode.get(), [0, 1], [-44, 0]),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,22 +108,22 @@ export function MessageInput({
|
|||||||
const measurement = measure(inputRef)
|
const measurement = measure(inputRef)
|
||||||
if (!measurement) return
|
if (!measurement) return
|
||||||
|
|
||||||
const max = windowHeight - -keyboardHeight.value - topInset - 150
|
const max = windowHeight - -keyboardHeight.get() - topInset - 150
|
||||||
const availableSpace = max - measurement.height
|
const availableSpace = max - measurement.height
|
||||||
|
|
||||||
maxHeight.value = max
|
maxHeight.set(max)
|
||||||
isInputScrollable.value = availableSpace < 30
|
isInputScrollable.set(availableSpace < 30)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[windowHeight, topInset],
|
[windowHeight, topInset],
|
||||||
)
|
)
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
maxHeight: maxHeight.value,
|
maxHeight: maxHeight.get(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const animatedProps = useAnimatedProps(() => ({
|
const animatedProps = useAnimatedProps(() => ({
|
||||||
scrollEnabled: isInputScrollable.value,
|
scrollEnabled: isInputScrollable.get(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export function MessagesList({
|
|||||||
(_: number, height: number) => {
|
(_: number, height: number) => {
|
||||||
// Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the
|
// Because web does not have `maintainVisibleContentPosition` support, we will need to manually scroll to the
|
||||||
// previous off whenever we add new content to the previous offset whenever we add new content to the list.
|
// previous off whenever we add new content to the previous offset whenever we add new content to the list.
|
||||||
if (isWeb && isAtTop.value && hasScrolled) {
|
if (isWeb && isAtTop.get() && hasScrolled) {
|
||||||
flatListRef.current?.scrollToOffset({
|
flatListRef.current?.scrollToOffset({
|
||||||
offset: height - prevContentHeight.current,
|
offset: height - prevContentHeight.current,
|
||||||
animated: false,
|
animated: false,
|
||||||
@@ -153,7 +153,7 @@ export function MessagesList({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This number _must_ be the height of the MaybeLoader component
|
// This number _must_ be the height of the MaybeLoader component
|
||||||
if (height > 50 && isAtBottom.value) {
|
if (height > 50 && isAtBottom.get()) {
|
||||||
// If the size of the content is changing by more than the height of the screen, then we don't
|
// If the size of the content is changing by more than the height of the screen, then we don't
|
||||||
// want to scroll further than the start of all the new content. Since we are storing the previous offset,
|
// want to scroll further than the start of all the new content. Since we are storing the previous offset,
|
||||||
// we can just scroll the user to that offset and add a little bit of padding. We'll also show the pill
|
// we can just scroll the user to that offset and add a little bit of padding. We'll also show the pill
|
||||||
@@ -161,7 +161,7 @@ export function MessagesList({
|
|||||||
if (
|
if (
|
||||||
didBackground.current &&
|
didBackground.current &&
|
||||||
hasScrolled &&
|
hasScrolled &&
|
||||||
height - prevContentHeight.current > layoutHeight.value - 50 &&
|
height - prevContentHeight.current > layoutHeight.get() - 50 &&
|
||||||
convoState.items.length - prevItemCount.current > 1
|
convoState.items.length - prevItemCount.current > 1
|
||||||
) {
|
) {
|
||||||
flatListRef.current?.scrollToOffset({
|
flatListRef.current?.scrollToOffset({
|
||||||
@@ -209,7 +209,7 @@ export function MessagesList({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const onStartReached = useCallback(() => {
|
const onStartReached = useCallback(() => {
|
||||||
if (hasScrolled && prevContentHeight.current > layoutHeight.value) {
|
if (hasScrolled && prevContentHeight.current > layoutHeight.get()) {
|
||||||
convoState.fetchMessageHistory()
|
convoState.fetchMessageHistory()
|
||||||
}
|
}
|
||||||
}, [convoState, hasScrolled, layoutHeight])
|
}, [convoState, hasScrolled, layoutHeight])
|
||||||
@@ -217,18 +217,18 @@ export function MessagesList({
|
|||||||
const onScroll = React.useCallback(
|
const onScroll = React.useCallback(
|
||||||
(e: ReanimatedScrollEvent) => {
|
(e: ReanimatedScrollEvent) => {
|
||||||
'worklet'
|
'worklet'
|
||||||
layoutHeight.value = e.layoutMeasurement.height
|
layoutHeight.set(e.layoutMeasurement.height)
|
||||||
const bottomOffset = e.contentOffset.y + e.layoutMeasurement.height
|
const bottomOffset = e.contentOffset.y + e.layoutMeasurement.height
|
||||||
|
|
||||||
// Most apps have a little bit of space the user can scroll past while still automatically scrolling ot the bottom
|
// Most apps have a little bit of space the user can scroll past while still automatically scrolling ot the bottom
|
||||||
// when a new message is added, hence the 100 pixel offset
|
// when a new message is added, hence the 100 pixel offset
|
||||||
isAtBottom.value = e.contentSize.height - 100 < bottomOffset
|
isAtBottom.set(e.contentSize.height - 100 < bottomOffset)
|
||||||
isAtTop.value = e.contentOffset.y <= 1
|
isAtTop.set(e.contentOffset.y <= 1)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
newMessagesPill.show &&
|
newMessagesPill.show &&
|
||||||
(e.contentOffset.y > newMessagesPill.startContentOffset + 200 ||
|
(e.contentOffset.y > newMessagesPill.startContentOffset + 200 ||
|
||||||
isAtBottom.value)
|
isAtBottom.get())
|
||||||
) {
|
) {
|
||||||
runOnJS(setNewMessagesPill)({
|
runOnJS(setNewMessagesPill)({
|
||||||
show: false,
|
show: false,
|
||||||
@@ -256,28 +256,28 @@ export function MessagesList({
|
|||||||
// Immediate updates - like opening the emoji picker - will have a duration of zero. In those cases, we should
|
// Immediate updates - like opening the emoji picker - will have a duration of zero. In those cases, we should
|
||||||
// just update the height here instead of having the `onMove` event do it (that event will not fire!)
|
// just update the height here instead of having the `onMove` event do it (that event will not fire!)
|
||||||
if (e.duration === 0) {
|
if (e.duration === 0) {
|
||||||
layoutScrollWithoutAnimation.value = true
|
layoutScrollWithoutAnimation.set(true)
|
||||||
keyboardHeight.value = e.height
|
keyboardHeight.set(e.height)
|
||||||
} else {
|
} else {
|
||||||
keyboardIsOpening.value = true
|
keyboardIsOpening.set(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMove: e => {
|
onMove: e => {
|
||||||
'worklet'
|
'worklet'
|
||||||
keyboardHeight.value = e.height
|
keyboardHeight.set(e.height)
|
||||||
if (e.height > bottomOffset) {
|
if (e.height > bottomOffset) {
|
||||||
scrollTo(flatListRef, 0, 1e7, false)
|
scrollTo(flatListRef, 0, 1e7, false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onEnd: () => {
|
onEnd: () => {
|
||||||
'worklet'
|
'worklet'
|
||||||
keyboardIsOpening.value = false
|
keyboardIsOpening.set(false)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const animatedListStyle = useAnimatedStyle(() => ({
|
const animatedListStyle = useAnimatedStyle(() => ({
|
||||||
marginBottom:
|
marginBottom:
|
||||||
keyboardHeight.value > bottomOffset ? keyboardHeight.value : bottomOffset,
|
keyboardHeight.get() > bottomOffset ? keyboardHeight.get() : bottomOffset,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// -- Message sending
|
// -- Message sending
|
||||||
@@ -363,13 +363,13 @@ export function MessagesList({
|
|||||||
// -- List layout changes (opening emoji keyboard, etc.)
|
// -- List layout changes (opening emoji keyboard, etc.)
|
||||||
const onListLayout = React.useCallback(
|
const onListLayout = React.useCallback(
|
||||||
(e: LayoutChangeEvent) => {
|
(e: LayoutChangeEvent) => {
|
||||||
layoutHeight.value = e.nativeEvent.layout.height
|
layoutHeight.set(e.nativeEvent.layout.height)
|
||||||
|
|
||||||
if (isWeb || !keyboardIsOpening.value) {
|
if (isWeb || !keyboardIsOpening.get()) {
|
||||||
flatListRef.current?.scrollToEnd({
|
flatListRef.current?.scrollToEnd({
|
||||||
animated: !layoutScrollWithoutAnimation.value,
|
animated: !layoutScrollWithoutAnimation.get(),
|
||||||
})
|
})
|
||||||
layoutScrollWithoutAnimation.value = false
|
layoutScrollWithoutAnimation.set(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function GrowableAvatarInner({
|
|||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
scale: interpolate(scrollY.value, [-150, 0], [1.2, 1], {
|
scale: interpolate(scrollY.get(), [-150, 0], [1.2, 1], {
|
||||||
extrapolateRight: Extrapolation.CLAMP,
|
extrapolateRight: Extrapolation.CLAMP,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ function GrowableBannerInner({
|
|||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
scale: interpolate(scrollY.value, [-150, 0], [2, 1], {
|
scale: interpolate(scrollY.get(), [-150, 0], [2, 1], {
|
||||||
extrapolateRight: Extrapolation.CLAMP,
|
extrapolateRight: Extrapolation.CLAMP,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -76,7 +76,7 @@ function GrowableBannerInner({
|
|||||||
const animatedBlurViewProps = useAnimatedProps(() => {
|
const animatedBlurViewProps = useAnimatedProps(() => {
|
||||||
return {
|
return {
|
||||||
intensity: interpolate(
|
intensity: interpolate(
|
||||||
scrollY.value,
|
scrollY.get(),
|
||||||
[-300, -65, -15],
|
[-300, -65, -15],
|
||||||
[50, 40, 0],
|
[50, 40, 0],
|
||||||
Extrapolation.CLAMP,
|
Extrapolation.CLAMP,
|
||||||
@@ -85,16 +85,17 @@ function GrowableBannerInner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const animatedSpinnerStyle = useAnimatedStyle(() => {
|
const animatedSpinnerStyle = useAnimatedStyle(() => {
|
||||||
|
const scrollYValue = scrollY.get()
|
||||||
return {
|
return {
|
||||||
display: scrollY.value < 0 ? 'flex' : 'none',
|
display: scrollYValue < 0 ? 'flex' : 'none',
|
||||||
opacity: interpolate(
|
opacity: interpolate(
|
||||||
scrollY.value,
|
scrollYValue,
|
||||||
[-60, -15],
|
[-60, -15],
|
||||||
[1, 0],
|
[1, 0],
|
||||||
Extrapolation.CLAMP,
|
Extrapolation.CLAMP,
|
||||||
),
|
),
|
||||||
transform: [
|
transform: [
|
||||||
{translateY: interpolate(scrollY.value, [-150, 0], [-75, 0])},
|
{translateY: interpolate(scrollYValue, [-150, 0], [-75, 0])},
|
||||||
{rotate: '90deg'},
|
{rotate: '90deg'},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,7 @@ function GrowableBannerInner({
|
|||||||
const animatedBackButtonStyle = useAnimatedStyle(() => ({
|
const animatedBackButtonStyle = useAnimatedStyle(() => ({
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: interpolate(scrollY.value, [-150, 60], [-150, 60], {
|
translateY: interpolate(scrollY.get(), [-150, 60], [-150, 60], {
|
||||||
extrapolateRight: Extrapolation.CLAMP,
|
extrapolateRight: Extrapolation.CLAMP,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -168,7 +169,7 @@ function useShouldAnimateSpinner({
|
|||||||
const stickyIsOverscrolled = useStickyToggle(isOverscrolled, 10)
|
const stickyIsOverscrolled = useStickyToggle(isOverscrolled, 10)
|
||||||
|
|
||||||
useAnimatedReaction(
|
useAnimatedReaction(
|
||||||
() => scrollY.value < -5,
|
() => scrollY.get() < -5,
|
||||||
(value, prevValue) => {
|
(value, prevValue) => {
|
||||||
if (value !== prevValue) {
|
if (value !== prevValue) {
|
||||||
runOnJS(setIsOverscrolled)(value)
|
runOnJS(setIsOverscrolled)(value)
|
||||||
|
|||||||
@@ -44,13 +44,17 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
'worklet'
|
'worklet'
|
||||||
// Cancel any existing animation
|
// Cancel any existing animation
|
||||||
cancelAnimation(headerMode)
|
cancelAnimation(headerMode)
|
||||||
headerMode.value = withSpring(v ? 1 : 0, {
|
headerMode.set(() =>
|
||||||
overshootClamping: true,
|
withSpring(v ? 1 : 0, {
|
||||||
})
|
overshootClamping: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
cancelAnimation(footerMode)
|
cancelAnimation(footerMode)
|
||||||
footerMode.value = withSpring(v ? 1 : 0, {
|
footerMode.set(() =>
|
||||||
overshootClamping: true,
|
withSpring(v ? 1 : 0, {
|
||||||
})
|
overshootClamping: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
[headerMode, footerMode],
|
[headerMode, footerMode],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1267,12 +1267,12 @@ function useScrollTracker({
|
|||||||
const contentHeight = useSharedValue(0)
|
const contentHeight = useSharedValue(0)
|
||||||
|
|
||||||
const hasScrolledToTop = useDerivedValue(() =>
|
const hasScrolledToTop = useDerivedValue(() =>
|
||||||
withTiming(contentOffset.value === 0 ? 1 : 0),
|
withTiming(contentOffset.get() === 0 ? 1 : 0),
|
||||||
)
|
)
|
||||||
|
|
||||||
const hasScrolledToBottom = useDerivedValue(() =>
|
const hasScrolledToBottom = useDerivedValue(() =>
|
||||||
withTiming(
|
withTiming(
|
||||||
contentHeight.value - contentOffset.value - 5 <= scrollViewHeight.value
|
contentHeight.get() - contentOffset.get() - 5 <= scrollViewHeight.get()
|
||||||
? 1
|
? 1
|
||||||
: 0,
|
: 0,
|
||||||
),
|
),
|
||||||
@@ -1290,11 +1290,11 @@ function useScrollTracker({
|
|||||||
}) => {
|
}) => {
|
||||||
'worklet'
|
'worklet'
|
||||||
if (typeof newContentHeight === 'number')
|
if (typeof newContentHeight === 'number')
|
||||||
contentHeight.value = Math.floor(newContentHeight)
|
contentHeight.set(Math.floor(newContentHeight))
|
||||||
if (typeof newContentOffset === 'number')
|
if (typeof newContentOffset === 'number')
|
||||||
contentOffset.value = Math.floor(newContentOffset)
|
contentOffset.set(Math.floor(newContentOffset))
|
||||||
if (typeof newScrollViewHeight === 'number')
|
if (typeof newScrollViewHeight === 'number')
|
||||||
scrollViewHeight.value = Math.floor(newScrollViewHeight)
|
scrollViewHeight.set(Math.floor(newScrollViewHeight))
|
||||||
},
|
},
|
||||||
[contentHeight, contentOffset, scrollViewHeight],
|
[contentHeight, contentOffset, scrollViewHeight],
|
||||||
)
|
)
|
||||||
@@ -1310,21 +1310,22 @@ function useScrollTracker({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const onScrollViewContentSizeChange = useCallback(
|
const onScrollViewContentSizeChangeUIThread = useCallback(
|
||||||
(_width: number, height: number) => {
|
(newContentHeight: number) => {
|
||||||
if (stickyBottom && height > contentHeight.value) {
|
'worklet'
|
||||||
|
const oldContentHeight = contentHeight.get()
|
||||||
|
let shouldScrollToBottom = false
|
||||||
|
if (stickyBottom && newContentHeight > oldContentHeight) {
|
||||||
const isFairlyCloseToBottom =
|
const isFairlyCloseToBottom =
|
||||||
contentHeight.value - contentOffset.value - 100 <=
|
oldContentHeight - contentOffset.get() - 100 <= scrollViewHeight.get()
|
||||||
scrollViewHeight.value
|
|
||||||
if (isFairlyCloseToBottom) {
|
if (isFairlyCloseToBottom) {
|
||||||
runOnUI(() => {
|
shouldScrollToBottom = true
|
||||||
scrollTo(scrollViewRef, 0, contentHeight.value, true)
|
|
||||||
})()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
showHideBottomBorder({
|
showHideBottomBorder({newContentHeight})
|
||||||
newContentHeight: height,
|
if (shouldScrollToBottom) {
|
||||||
})
|
scrollTo(scrollViewRef, 0, newContentHeight, true)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
showHideBottomBorder,
|
showHideBottomBorder,
|
||||||
@@ -1336,6 +1337,13 @@ function useScrollTracker({
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const onScrollViewContentSizeChange = useCallback(
|
||||||
|
(_width: number, height: number) => {
|
||||||
|
runOnUI(onScrollViewContentSizeChangeUIThread)(height)
|
||||||
|
},
|
||||||
|
[onScrollViewContentSizeChangeUIThread],
|
||||||
|
)
|
||||||
|
|
||||||
const onScrollViewLayout = useCallback(
|
const onScrollViewLayout = useCallback(
|
||||||
(evt: LayoutChangeEvent) => {
|
(evt: LayoutChangeEvent) => {
|
||||||
showHideBottomBorder({
|
showHideBottomBorder({
|
||||||
@@ -1349,7 +1357,7 @@ function useScrollTracker({
|
|||||||
return {
|
return {
|
||||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
borderColor: interpolateColor(
|
borderColor: interpolateColor(
|
||||||
hasScrolledToTop.value,
|
hasScrolledToTop.get(),
|
||||||
[0, 1],
|
[0, 1],
|
||||||
[t.atoms.border_contrast_medium.borderColor, 'transparent'],
|
[t.atoms.border_contrast_medium.borderColor, 'transparent'],
|
||||||
),
|
),
|
||||||
@@ -1359,7 +1367,7 @@ function useScrollTracker({
|
|||||||
return {
|
return {
|
||||||
borderTopWidth: StyleSheet.hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
borderColor: interpolateColor(
|
borderColor: interpolateColor(
|
||||||
hasScrolledToBottom.value,
|
hasScrolledToBottom.get(),
|
||||||
[0, 1],
|
[0, 1],
|
||||||
[t.atoms.border_contrast_medium.borderColor, 'transparent'],
|
[t.atoms.border_contrast_medium.borderColor, 'transparent'],
|
||||||
),
|
),
|
||||||
@@ -1604,7 +1612,7 @@ function VideoUploadToolbar({state}: {state: VideoState}) {
|
|||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => {
|
const animatedStyle = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
transform: [{rotateZ: `${rotate.value}deg`}],
|
transform: [{rotateZ: `${rotate.get()}deg`}],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function HomeHeaderLayoutDesktopAndTablet({
|
|||||||
{tabBarAnchor}
|
{tabBarAnchor}
|
||||||
<Animated.View
|
<Animated.View
|
||||||
onLayout={e => {
|
onLayout={e => {
|
||||||
headerHeight.value = e.nativeEvent.layout.height
|
headerHeight.set(e.nativeEvent.layout.height)
|
||||||
}}
|
}}
|
||||||
style={[
|
style={[
|
||||||
t.atoms.bg,
|
t.atoms.bg,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function HomeHeaderLayoutMobile({
|
|||||||
<Animated.View
|
<Animated.View
|
||||||
style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
|
style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
|
||||||
onLayout={e => {
|
onLayout={e => {
|
||||||
headerHeight.value = e.nativeEvent.layout.height
|
headerHeight.set(e.nativeEvent.layout.height)
|
||||||
}}>
|
}}>
|
||||||
<View style={[pal.view, styles.topBar]}>
|
<View style={[pal.view, styles.topBar]}>
|
||||||
<View style={[pal.view, {width: 100}]}>
|
<View style={[pal.view, {width: 100}]}>
|
||||||
|
|||||||
@@ -87,11 +87,11 @@ const ImageItem = ({
|
|||||||
// Note: DO NOT move any logic reading animated values outside this function.
|
// Note: DO NOT move any logic reading animated values outside this function.
|
||||||
useAnimatedReaction(
|
useAnimatedReaction(
|
||||||
() => {
|
() => {
|
||||||
if (pinchScale.value !== 1) {
|
if (pinchScale.get() !== 1) {
|
||||||
// We're currently pinching.
|
// We're currently pinching.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const [, , committedScale] = readTransform(committedTransform.value)
|
const [, , committedScale] = readTransform(committedTransform.get())
|
||||||
if (committedScale !== 1) {
|
if (committedScale !== 1) {
|
||||||
// We started from a pinched in state.
|
// We started from a pinched in state.
|
||||||
return true
|
return true
|
||||||
@@ -147,10 +147,10 @@ const ImageItem = ({
|
|||||||
.onStart(e => {
|
.onStart(e => {
|
||||||
'worklet'
|
'worklet'
|
||||||
const screenSize = measureSafeArea()
|
const screenSize = measureSafeArea()
|
||||||
pinchOrigin.value = {
|
pinchOrigin.set({
|
||||||
x: e.focalX - screenSize.width / 2,
|
x: e.focalX - screenSize.width / 2,
|
||||||
y: e.focalY - screenSize.height / 2,
|
y: e.focalY - screenSize.height / 2,
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
.onChange(e => {
|
.onChange(e => {
|
||||||
'worklet'
|
'worklet'
|
||||||
@@ -160,7 +160,7 @@ const ImageItem = ({
|
|||||||
}
|
}
|
||||||
// Don't let the picture zoom in so close that it gets blurry.
|
// 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.
|
// 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(
|
const maxCommittedScale = Math.max(
|
||||||
MIN_SCREEN_ZOOM,
|
MIN_SCREEN_ZOOM,
|
||||||
(imageDimensions.width / screenSize.width) * MAX_ORIGINAL_IMAGE_ZOOM,
|
(imageDimensions.width / screenSize.width) * MAX_ORIGINAL_IMAGE_ZOOM,
|
||||||
@@ -171,20 +171,21 @@ const ImageItem = ({
|
|||||||
Math.max(minPinchScale, e.scale),
|
Math.max(minPinchScale, e.scale),
|
||||||
maxPinchScale,
|
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.
|
// 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.
|
// Calculate where we'll end up so we know how much to translate back to stay in bounds.
|
||||||
const t = createTransform()
|
const t = createTransform()
|
||||||
prependPan(t, panTranslation.value)
|
prependPan(t, panTranslation.get())
|
||||||
prependPinch(t, nextPinchScale, pinchOrigin.value, pinchTranslation.value)
|
prependPinch(t, nextPinchScale, pinchOrigin.get(), pinchTranslation.get())
|
||||||
prependTransform(t, committedTransform.value)
|
prependTransform(t, committedTransform.get())
|
||||||
const [dx, dy] = getExtraTranslationToStayInBounds(t, screenSize)
|
const [dx, dy] = getExtraTranslationToStayInBounds(t, screenSize)
|
||||||
if (dx !== 0 || dy !== 0) {
|
if (dx !== 0 || dy !== 0) {
|
||||||
pinchTranslation.value = {
|
const pt = pinchTranslation.get()
|
||||||
x: pinchTranslation.value.x + dx,
|
pinchTranslation.set({
|
||||||
y: pinchTranslation.value.y + dy,
|
x: pt.x + dx,
|
||||||
}
|
y: pt.y + dy,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.onEnd(() => {
|
.onEnd(() => {
|
||||||
@@ -193,18 +194,18 @@ const ImageItem = ({
|
|||||||
let t = createTransform()
|
let t = createTransform()
|
||||||
prependPinch(
|
prependPinch(
|
||||||
t,
|
t,
|
||||||
pinchScale.value,
|
pinchScale.get(),
|
||||||
pinchOrigin.value,
|
pinchOrigin.get(),
|
||||||
pinchTranslation.value,
|
pinchTranslation.get(),
|
||||||
)
|
)
|
||||||
prependTransform(t, committedTransform.value)
|
prependTransform(t, committedTransform.get())
|
||||||
applyRounding(t)
|
applyRounding(t)
|
||||||
committedTransform.value = t
|
committedTransform.set(t)
|
||||||
|
|
||||||
// Reset just the pinch.
|
// Reset just the pinch.
|
||||||
pinchScale.value = 1
|
pinchScale.set(1)
|
||||||
pinchOrigin.value = {x: 0, y: 0}
|
pinchOrigin.set({x: 0, y: 0})
|
||||||
pinchTranslation.value = {x: 0, y: 0}
|
pinchTranslation.set({x: 0, y: 0})
|
||||||
})
|
})
|
||||||
|
|
||||||
const pan = Gesture.Pan()
|
const pan = Gesture.Pan()
|
||||||
@@ -223,29 +224,29 @@ const ImageItem = ({
|
|||||||
prependPan(t, nextPanTranslation)
|
prependPan(t, nextPanTranslation)
|
||||||
prependPinch(
|
prependPinch(
|
||||||
t,
|
t,
|
||||||
pinchScale.value,
|
pinchScale.get(),
|
||||||
pinchOrigin.value,
|
pinchOrigin.get(),
|
||||||
pinchTranslation.value,
|
pinchTranslation.get(),
|
||||||
)
|
)
|
||||||
prependTransform(t, committedTransform.value)
|
prependTransform(t, committedTransform.get())
|
||||||
|
|
||||||
// Prevent panning from going out of bounds.
|
// Prevent panning from going out of bounds.
|
||||||
const [dx, dy] = getExtraTranslationToStayInBounds(t, screenSize)
|
const [dx, dy] = getExtraTranslationToStayInBounds(t, screenSize)
|
||||||
nextPanTranslation.x += dx
|
nextPanTranslation.x += dx
|
||||||
nextPanTranslation.y += dy
|
nextPanTranslation.y += dy
|
||||||
panTranslation.value = nextPanTranslation
|
panTranslation.set(nextPanTranslation)
|
||||||
})
|
})
|
||||||
.onEnd(() => {
|
.onEnd(() => {
|
||||||
'worklet'
|
'worklet'
|
||||||
// Commit just the pan.
|
// Commit just the pan.
|
||||||
let t = createTransform()
|
let t = createTransform()
|
||||||
prependPan(t, panTranslation.value)
|
prependPan(t, panTranslation.get())
|
||||||
prependTransform(t, committedTransform.value)
|
prependTransform(t, committedTransform.get())
|
||||||
applyRounding(t)
|
applyRounding(t)
|
||||||
committedTransform.value = t
|
committedTransform.set(t)
|
||||||
|
|
||||||
// Reset just the pan.
|
// Reset just the pan.
|
||||||
panTranslation.value = {x: 0, y: 0}
|
panTranslation.set({x: 0, y: 0})
|
||||||
})
|
})
|
||||||
|
|
||||||
const singleTap = Gesture.Tap().onEnd(() => {
|
const singleTap = Gesture.Tap().onEnd(() => {
|
||||||
@@ -261,11 +262,11 @@ const ImageItem = ({
|
|||||||
if (!imageDimensions || !imageAspect) {
|
if (!imageDimensions || !imageAspect) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const [, , committedScale] = readTransform(committedTransform.value)
|
const [, , committedScale] = readTransform(committedTransform.get())
|
||||||
if (committedScale !== 1) {
|
if (committedScale !== 1) {
|
||||||
// Go back to 1:1 using the identity vector.
|
// Go back to 1:1 using the identity vector.
|
||||||
let t = createTransform()
|
let t = createTransform()
|
||||||
committedTransform.value = withClampedSpring(t)
|
committedTransform.set(withClampedSpring(t))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +300,7 @@ const ImageItem = ({
|
|||||||
)
|
)
|
||||||
const finalTransform = createTransform()
|
const finalTransform = createTransform()
|
||||||
prependPinch(finalTransform, scale, origin, {x: dx, y: dy})
|
prependPinch(finalTransform, scale, origin, {x: dx, y: dy})
|
||||||
committedTransform.value = withClampedSpring(finalTransform)
|
committedTransform.set(withClampedSpring(finalTransform))
|
||||||
})
|
})
|
||||||
|
|
||||||
const composedGesture = isScrollViewBeingDragged
|
const composedGesture = isScrollViewBeingDragged
|
||||||
@@ -313,13 +314,13 @@ const ImageItem = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const containerStyle = useAnimatedStyle(() => {
|
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.
|
// 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.
|
// This is matrix multiplication, so operations are applied in the reverse order.
|
||||||
let t = createTransform()
|
let t = createTransform()
|
||||||
prependPan(t, panTranslation.value)
|
prependPan(t, panTranslation.get())
|
||||||
prependPinch(t, pinchScale.value, pinchOrigin.value, pinchTranslation.value)
|
prependPinch(t, pinchScale.get(), pinchOrigin.get(), pinchTranslation.get())
|
||||||
prependTransform(t, committedTransform.value)
|
prependTransform(t, committedTransform.get())
|
||||||
const [translateX, translateY, scale] = readTransform(t)
|
const [translateX, translateY, scale] = readTransform(t)
|
||||||
const manipulationTransform = [
|
const manipulationTransform = [
|
||||||
{translateX},
|
{translateX},
|
||||||
@@ -338,7 +339,7 @@ const ImageItem = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const imageCropStyle = useAnimatedStyle(() => {
|
const imageCropStyle = useAnimatedStyle(() => {
|
||||||
const {cropFrameTransform} = transforms.value
|
const {cropFrameTransform} = transforms.get()
|
||||||
return {
|
return {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -347,7 +348,7 @@ const ImageItem = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const imageStyle = useAnimatedStyle(() => {
|
const imageStyle = useAnimatedStyle(() => {
|
||||||
const {cropContentTransform} = transforms.value
|
const {cropContentTransform} = transforms.get()
|
||||||
return {
|
return {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
transform: cropContentTransform,
|
transform: cropContentTransform,
|
||||||
@@ -359,7 +360,7 @@ const ImageItem = ({
|
|||||||
const [hasLoaded, setHasLoaded] = useState(false)
|
const [hasLoaded, setHasLoaded] = useState(false)
|
||||||
useAnimatedReaction(
|
useAnimatedReaction(
|
||||||
() => {
|
() => {
|
||||||
return transforms.value.isResting && !hasLoaded
|
return transforms.get().isResting && !hasLoaded
|
||||||
},
|
},
|
||||||
(show, prevShow) => {
|
(show, prevShow) => {
|
||||||
if (show && !prevShow) {
|
if (show && !prevShow) {
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ const ImageItem = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const containerStyle = useAnimatedStyle(() => {
|
const containerStyle = useAnimatedStyle(() => {
|
||||||
const {scaleAndMoveTransform, isHidden} = transforms.value
|
const {scaleAndMoveTransform, isHidden} = transforms.get()
|
||||||
return {
|
return {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
transform: scaleAndMoveTransform,
|
transform: scaleAndMoveTransform,
|
||||||
@@ -158,7 +158,7 @@ const ImageItem = ({
|
|||||||
|
|
||||||
const imageCropStyle = useAnimatedStyle(() => {
|
const imageCropStyle = useAnimatedStyle(() => {
|
||||||
const screenSize = measureSafeArea()
|
const screenSize = measureSafeArea()
|
||||||
const {cropFrameTransform} = transforms.value
|
const {cropFrameTransform} = transforms.get()
|
||||||
return {
|
return {
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
transform: cropFrameTransform,
|
transform: cropFrameTransform,
|
||||||
@@ -171,7 +171,7 @@ const ImageItem = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const imageStyle = useAnimatedStyle(() => {
|
const imageStyle = useAnimatedStyle(() => {
|
||||||
const {cropContentTransform} = transforms.value
|
const {cropContentTransform} = transforms.get()
|
||||||
return {
|
return {
|
||||||
transform: cropContentTransform,
|
transform: cropContentTransform,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -184,7 +184,7 @@ const ImageItem = ({
|
|||||||
const [hasLoaded, setHasLoaded] = useState(false)
|
const [hasLoaded, setHasLoaded] = useState(false)
|
||||||
useAnimatedReaction(
|
useAnimatedReaction(
|
||||||
() => {
|
() => {
|
||||||
return transforms.value.isResting && !hasLoaded
|
return transforms.get().isResting && !hasLoaded
|
||||||
},
|
},
|
||||||
(show, prevShow) => {
|
(show, prevShow) => {
|
||||||
if (show && !prevShow) {
|
if (show && !prevShow) {
|
||||||
|
|||||||
@@ -109,18 +109,22 @@ export default function ImageViewRoot({
|
|||||||
|
|
||||||
// https://github.com/software-mansion/react-native-reanimated/issues/6677
|
// https://github.com/software-mansion/react-native-reanimated/issues/6677
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
openProgress.value = canAnimate ? withClampedSpring(1, SLOW_SPRING) : 1
|
openProgress.set(() =>
|
||||||
|
canAnimate ? withClampedSpring(1, SLOW_SPRING) : 1,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
return () => {
|
return () => {
|
||||||
// https://github.com/software-mansion/react-native-reanimated/issues/6677
|
// https://github.com/software-mansion/react-native-reanimated/issues/6677
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
openProgress.value = canAnimate ? withClampedSpring(0, SLOW_SPRING) : 0
|
openProgress.set(() =>
|
||||||
|
canAnimate ? withClampedSpring(0, SLOW_SPRING) : 0,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [nextLightbox, openProgress])
|
}, [nextLightbox, openProgress])
|
||||||
|
|
||||||
useAnimatedReaction(
|
useAnimatedReaction(
|
||||||
() => openProgress.value === 0,
|
() => openProgress.get() === 0,
|
||||||
(isGone, wasGone) => {
|
(isGone, wasGone) => {
|
||||||
if (isGone && !wasGone) {
|
if (isGone && !wasGone) {
|
||||||
runOnJS(setActiveLightbox)(null)
|
runOnJS(setActiveLightbox)(null)
|
||||||
@@ -130,7 +134,7 @@ export default function ImageViewRoot({
|
|||||||
|
|
||||||
const onFlyAway = React.useCallback(() => {
|
const onFlyAway = React.useCallback(() => {
|
||||||
'worklet'
|
'worklet'
|
||||||
openProgress.value = 0
|
openProgress.set(0)
|
||||||
runOnJS(onRequestClose)()
|
runOnJS(onRequestClose)()
|
||||||
}, [onRequestClose, openProgress])
|
}, [onRequestClose, openProgress])
|
||||||
|
|
||||||
@@ -187,7 +191,7 @@ function ImageView({
|
|||||||
const isFlyingAway = useSharedValue(false)
|
const isFlyingAway = useSharedValue(false)
|
||||||
|
|
||||||
const containerStyle = useAnimatedStyle(() => {
|
const containerStyle = useAnimatedStyle(() => {
|
||||||
if (openProgress.value < 1 || isFlyingAway.value) {
|
if (openProgress.get() < 1 || isFlyingAway.get()) {
|
||||||
return {pointerEvents: 'none'}
|
return {pointerEvents: 'none'}
|
||||||
}
|
}
|
||||||
return {pointerEvents: 'auto'}
|
return {pointerEvents: 'auto'}
|
||||||
@@ -196,11 +200,12 @@ function ImageView({
|
|||||||
const backdropStyle = useAnimatedStyle(() => {
|
const backdropStyle = useAnimatedStyle(() => {
|
||||||
const screenSize = measure(safeAreaRef)
|
const screenSize = measure(safeAreaRef)
|
||||||
let opacity = 1
|
let opacity = 1
|
||||||
if (openProgress.value < 1) {
|
const openProgressValue = openProgress.get()
|
||||||
opacity = Math.sqrt(openProgress.value)
|
if (openProgressValue < 1) {
|
||||||
|
opacity = Math.sqrt(openProgressValue)
|
||||||
} else if (screenSize) {
|
} else if (screenSize) {
|
||||||
const dragProgress = Math.min(
|
const dragProgress = Math.min(
|
||||||
Math.abs(dismissSwipeTranslateY.value) / (screenSize.height / 2),
|
Math.abs(dismissSwipeTranslateY.get()) / (screenSize.height / 2),
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
opacity -= dragProgress
|
opacity -= dragProgress
|
||||||
@@ -212,11 +217,11 @@ function ImageView({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const animatedHeaderStyle = useAnimatedStyle(() => {
|
const animatedHeaderStyle = useAnimatedStyle(() => {
|
||||||
const show = showControls && dismissSwipeTranslateY.value === 0
|
const show = showControls && dismissSwipeTranslateY.get() === 0
|
||||||
return {
|
return {
|
||||||
pointerEvents: show ? 'box-none' : 'none',
|
pointerEvents: show ? 'box-none' : 'none',
|
||||||
opacity: withClampedSpring(
|
opacity: withClampedSpring(
|
||||||
show && openProgress.value === 1 ? 1 : 0,
|
show && openProgress.get() === 1 ? 1 : 0,
|
||||||
FAST_SPRING,
|
FAST_SPRING,
|
||||||
),
|
),
|
||||||
transform: [
|
transform: [
|
||||||
@@ -227,12 +232,12 @@ function ImageView({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const animatedFooterStyle = useAnimatedStyle(() => {
|
const animatedFooterStyle = useAnimatedStyle(() => {
|
||||||
const show = showControls && dismissSwipeTranslateY.value === 0
|
const show = showControls && dismissSwipeTranslateY.get() === 0
|
||||||
return {
|
return {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
pointerEvents: show ? 'box-none' : 'none',
|
pointerEvents: show ? 'box-none' : 'none',
|
||||||
opacity: withClampedSpring(
|
opacity: withClampedSpring(
|
||||||
show && openProgress.value === 1 ? 1 : 0,
|
show && openProgress.get() === 1 ? 1 : 0,
|
||||||
FAST_SPRING,
|
FAST_SPRING,
|
||||||
),
|
),
|
||||||
transform: [
|
transform: [
|
||||||
@@ -259,7 +264,7 @@ function ImageView({
|
|||||||
const screenSize = measure(safeAreaRef)
|
const screenSize = measure(safeAreaRef)
|
||||||
return (
|
return (
|
||||||
!screenSize ||
|
!screenSize ||
|
||||||
Math.abs(dismissSwipeTranslateY.value) > screenSize.height
|
Math.abs(dismissSwipeTranslateY.get()) > screenSize.height
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
(isOut, wasOut) => {
|
(isOut, wasOut) => {
|
||||||
@@ -397,10 +402,11 @@ function LightboxImage({
|
|||||||
const transforms = useDerivedValue(() => {
|
const transforms = useDerivedValue(() => {
|
||||||
'worklet'
|
'worklet'
|
||||||
const safeArea = measureSafeArea()
|
const safeArea = measureSafeArea()
|
||||||
|
const openProgressValue = openProgress.get()
|
||||||
const dismissTranslateY =
|
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 {
|
return {
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
isResting: false,
|
isResting: false,
|
||||||
@@ -410,9 +416,9 @@ function LightboxImage({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActive && thumbRect && imageAspect && openProgress.value < 1) {
|
if (isActive && thumbRect && imageAspect && openProgressValue < 1) {
|
||||||
return interpolateTransform(
|
return interpolateTransform(
|
||||||
openProgress.value,
|
openProgressValue,
|
||||||
thumbRect,
|
thumbRect,
|
||||||
safeArea,
|
safeArea,
|
||||||
imageAspect,
|
imageAspect,
|
||||||
@@ -434,33 +440,37 @@ function LightboxImage({
|
|||||||
.maxPointers(1)
|
.maxPointers(1)
|
||||||
.onUpdate(e => {
|
.onUpdate(e => {
|
||||||
'worklet'
|
'worklet'
|
||||||
if (openProgress.value !== 1 || isFlyingAway.value) {
|
if (openProgress.get() !== 1 || isFlyingAway.get()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dismissSwipeTranslateY.value = e.translationY
|
dismissSwipeTranslateY.set(e.translationY)
|
||||||
})
|
})
|
||||||
.onEnd(e => {
|
.onEnd(e => {
|
||||||
'worklet'
|
'worklet'
|
||||||
if (openProgress.value !== 1 || isFlyingAway.value) {
|
if (openProgress.get() !== 1 || isFlyingAway.get()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (Math.abs(e.velocityY) > 200) {
|
if (Math.abs(e.velocityY) > 200) {
|
||||||
isFlyingAway.value = true
|
isFlyingAway.set(true)
|
||||||
if (dismissSwipeTranslateY.value === 0) {
|
if (dismissSwipeTranslateY.get() === 0) {
|
||||||
// HACK: If the initial value is 0, withDecay() animation doesn't start.
|
// 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.
|
// 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({
|
dismissSwipeTranslateY.set(() =>
|
||||||
velocity: e.velocityY,
|
withDecay({
|
||||||
velocityFactor: Math.max(3500 / Math.abs(e.velocityY), 1), // Speed up if it's too slow.
|
velocity: e.velocityY,
|
||||||
deceleration: 1, // Danger! This relies on the reaction below stopping it.
|
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 {
|
} else {
|
||||||
dismissSwipeTranslateY.value = withSpring(0, {
|
dismissSwipeTranslateY.set(() =>
|
||||||
stiffness: 700,
|
withSpring(0, {
|
||||||
damping: 50,
|
stiffness: 700,
|
||||||
})
|
damping: 50,
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -131,11 +131,11 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
|||||||
const lastForcedScrollY = useSharedValue(0)
|
const lastForcedScrollY = useSharedValue(0)
|
||||||
const adjustScrollForOtherPages = () => {
|
const adjustScrollForOtherPages = () => {
|
||||||
'worklet'
|
'worklet'
|
||||||
const currentScrollY = scrollY.value
|
const currentScrollY = scrollY.get()
|
||||||
const forcedScrollY = Math.min(currentScrollY, headerOnlyHeight)
|
const forcedScrollY = Math.min(currentScrollY, headerOnlyHeight)
|
||||||
if (lastForcedScrollY.value !== forcedScrollY) {
|
if (lastForcedScrollY.get() !== forcedScrollY) {
|
||||||
lastForcedScrollY.value = forcedScrollY
|
lastForcedScrollY.set(forcedScrollY)
|
||||||
const refs = scrollRefs.value
|
const refs = scrollRefs.get()
|
||||||
for (let i = 0; i < refs.length; i++) {
|
for (let i = 0; i < refs.length; i++) {
|
||||||
const scollRef = refs[i]
|
const scollRef = refs[i]
|
||||||
if (i !== currentPage && scollRef != null) {
|
if (i !== currentPage && scollRef != null) {
|
||||||
@@ -167,7 +167,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
|||||||
const isPossiblyInvalid =
|
const isPossiblyInvalid =
|
||||||
headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight
|
headerHeight > 0 && Math.round(nextScrollY * 2) / 2 === -headerHeight
|
||||||
if (!isPossiblyInvalid) {
|
if (!isPossiblyInvalid) {
|
||||||
scrollY.value = nextScrollY
|
scrollY.set(nextScrollY)
|
||||||
runOnJS(queueThrottledOnScroll)()
|
runOnJS(queueThrottledOnScroll)()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -246,7 +246,7 @@ let PagerTabBar = ({
|
|||||||
allowHeaderOverScroll?: boolean
|
allowHeaderOverScroll?: boolean
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const headerTransform = useAnimatedStyle(() => {
|
const headerTransform = useAnimatedStyle(() => {
|
||||||
const translateY = Math.min(scrollY.value, headerOnlyHeight) * -1
|
const translateY = Math.min(scrollY.get(), headerOnlyHeight) * -1
|
||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function createCustomBackdrop(
|
|||||||
// animated variables
|
// animated variables
|
||||||
const opacity = useAnimatedStyle(() => ({
|
const opacity = useAnimatedStyle(() => ({
|
||||||
opacity: interpolate(
|
opacity: interpolate(
|
||||||
animatedIndex.value, // current snap index
|
animatedIndex.get(), // current snap index
|
||||||
[-1, 0], // input range
|
[-1, 0], // input range
|
||||||
[0, 0.5], // output range
|
[0, 0.5], // output range
|
||||||
Extrapolation.CLAMP,
|
Extrapolation.CLAMP,
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ function ListImpl<ItemT>(
|
|||||||
onScrollFromContext?.(e, ctx)
|
onScrollFromContext?.(e, ctx)
|
||||||
|
|
||||||
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
|
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
|
||||||
if (isScrolledDown.value !== didScrollDown) {
|
if (isScrolledDown.get() !== didScrollDown) {
|
||||||
isScrolledDown.value = didScrollDown
|
isScrolledDown.set(didScrollDown)
|
||||||
if (onScrolledDownChange != null) {
|
if (onScrolledDownChange != null) {
|
||||||
runOnJS(handleScrolledDownChange)(didScrollDown)
|
runOnJS(handleScrolledDownChange)(didScrollDown)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
|||||||
(v: boolean) => {
|
(v: boolean) => {
|
||||||
'worklet'
|
'worklet'
|
||||||
cancelAnimation(headerMode)
|
cancelAnimation(headerMode)
|
||||||
headerMode.value = v ? V1.value : V0.value
|
headerMode.set(v ? V1.get() : V0.get())
|
||||||
},
|
},
|
||||||
[headerMode],
|
[headerMode],
|
||||||
)
|
)
|
||||||
@@ -52,9 +52,9 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
return listenToForcedWindowScroll(() => {
|
return listenToForcedWindowScroll(() => {
|
||||||
startDragOffset.value = null
|
startDragOffset.set(null)
|
||||||
startMode.value = null
|
startMode.set(null)
|
||||||
didJustRestoreScroll.value = true
|
didJustRestoreScroll.set(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -63,13 +63,14 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
|||||||
(e: NativeScrollEvent) => {
|
(e: NativeScrollEvent) => {
|
||||||
'worklet'
|
'worklet'
|
||||||
if (isNative) {
|
if (isNative) {
|
||||||
if (startDragOffset.value === null) {
|
const startDragOffsetValue = startDragOffset.get()
|
||||||
|
if (startDragOffsetValue === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const didScrollDown = e.contentOffset.y > startDragOffset.value
|
const didScrollDown = e.contentOffset.y > startDragOffsetValue
|
||||||
startDragOffset.value = null
|
startDragOffset.set(null)
|
||||||
startMode.value = null
|
startMode.set(null)
|
||||||
if (e.contentOffset.y < headerHeight.value) {
|
if (e.contentOffset.y < headerHeight.get()) {
|
||||||
// If we're close to the top, show the shell.
|
// If we're close to the top, show the shell.
|
||||||
setMode(false)
|
setMode(false)
|
||||||
} else if (didScrollDown) {
|
} else if (didScrollDown) {
|
||||||
@@ -77,7 +78,7 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
|||||||
setMode(true)
|
setMode(true)
|
||||||
} else {
|
} else {
|
||||||
// Snap to whichever state is the closest.
|
// 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) => {
|
(e: NativeScrollEvent) => {
|
||||||
'worklet'
|
'worklet'
|
||||||
if (isNative) {
|
if (isNative) {
|
||||||
startDragOffset.value = e.contentOffset.y
|
startDragOffset.set(e.contentOffset.y)
|
||||||
startMode.value = headerMode.value
|
startMode.set(headerMode.get())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[headerMode, startDragOffset, startMode],
|
[headerMode, startDragOffset, startMode],
|
||||||
@@ -123,10 +124,12 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
|||||||
(e: NativeScrollEvent) => {
|
(e: NativeScrollEvent) => {
|
||||||
'worklet'
|
'worklet'
|
||||||
if (isNative) {
|
if (isNative) {
|
||||||
if (startDragOffset.value === null || startMode.value === null) {
|
const startDragOffsetValue = startDragOffset.get()
|
||||||
|
const startModeValue = startMode.get()
|
||||||
|
if (startDragOffsetValue === null || startModeValue === null) {
|
||||||
if (
|
if (
|
||||||
headerMode.value !== 0 &&
|
headerMode.get() !== 0 &&
|
||||||
e.contentOffset.y < headerHeight.value
|
e.contentOffset.y < headerHeight.get()
|
||||||
) {
|
) {
|
||||||
// If we're close enough to the top, always show the shell.
|
// If we're close enough to the top, always show the shell.
|
||||||
// Even if we're not dragging.
|
// 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.
|
// The "mode" value is always between 0 and 1.
|
||||||
// Figure out how much to move it based on the current dragged distance.
|
// 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(
|
const dProgress = interpolate(
|
||||||
dy,
|
dy,
|
||||||
[-headerHeight.value, headerHeight.value],
|
[-headerHeight.get(), headerHeight.get()],
|
||||||
[-1, 1],
|
[-1, 1],
|
||||||
)
|
)
|
||||||
const newValue = clamp(startMode.value + dProgress, 0, 1)
|
const newValue = clamp(startModeValue + dProgress, 0, 1)
|
||||||
if (newValue !== headerMode.value) {
|
if (newValue !== headerMode.get()) {
|
||||||
// Manually adjust the value. This won't be (and shouldn't be) animated.
|
// Manually adjust the value. This won't be (and shouldn't be) animated.
|
||||||
// Cancel any any existing animation
|
// Cancel any any existing animation
|
||||||
cancelAnimation(headerMode)
|
cancelAnimation(headerMode)
|
||||||
headerMode.value = newValue
|
headerMode.set(newValue)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (didJustRestoreScroll.value) {
|
if (didJustRestoreScroll.get()) {
|
||||||
didJustRestoreScroll.value = false
|
didJustRestoreScroll.set(false)
|
||||||
// Don't hide/show navbar based on scroll restoratoin.
|
// Don't hide/show navbar based on scroll restoratoin.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// On the web, we don't try to follow the drag because we don't know when it ends.
|
// 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.
|
// Instead, show/hide immediately based on whether we're scrolling up or down.
|
||||||
const dy = e.contentOffset.y - (startDragOffset.value ?? 0)
|
const dy = e.contentOffset.y - (startDragOffset.get() ?? 0)
|
||||||
startDragOffset.value = e.contentOffset.y
|
startDragOffset.set(e.contentOffset.y)
|
||||||
|
|
||||||
if (dy < 0 || e.contentOffset.y < WEB_HIDE_SHELL_THRESHOLD) {
|
if (dy < 0 || e.contentOffset.y < WEB_HIDE_SHELL_THRESHOLD) {
|
||||||
setMode(false)
|
setMode(false)
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
|||||||
footerMinimalShellTransform,
|
footerMinimalShellTransform,
|
||||||
]}
|
]}
|
||||||
onLayout={e => {
|
onLayout={e => {
|
||||||
footerHeight.value = e.nativeEvent.layout.height
|
footerHeight.set(e.nativeEvent.layout.height)
|
||||||
}}>
|
}}>
|
||||||
{hasSession ? (
|
{hasSession ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user