65 lines
2.7 KiB
TypeScript
65 lines
2.7 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {useFocusEffect} from 'expo-router';
|
|
import {useCallback, useEffect, useRef} from 'react';
|
|
import {AppState, Platform} from 'react-native';
|
|
import {cancelAnimation, useReducedMotion, useSharedValue, useAnimatedStyle, withTiming} from 'react-native-reanimated';
|
|
|
|
import {useWindowDimensions} from './device';
|
|
|
|
/**
|
|
* Expo Router version - Uses focus/blur events instead of mount/unmount
|
|
* This handles the case where screens stay mounted when pushed/popped
|
|
*/
|
|
export const useScreenTransitionAnimation = (animated: boolean = true) => {
|
|
const {width} = useWindowDimensions();
|
|
const reducedMotion = useReducedMotion();
|
|
const shouldAnimate = animated && !reducedMotion;
|
|
const translateX = useSharedValue(shouldAnimate ? width : 0);
|
|
const animationDuration = Platform.OS === 'android' ? 250 : 350;
|
|
|
|
// Keep a ref so the useFocusEffect cleanup always reads the latest values
|
|
// without adding them as dependencies (which would re-trigger cleanup on rotation/motion changes)
|
|
const latestRef = useRef({width, shouldAnimate});
|
|
useEffect(() => {
|
|
latestRef.current = {width, shouldAnimate};
|
|
}, [width, shouldAnimate]);
|
|
|
|
const animatedStyle = useAnimatedStyle(() => ({
|
|
transform: [{translateX: translateX.value}],
|
|
}), []);
|
|
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
const {shouldAnimate: sa} = latestRef.current;
|
|
translateX.value = sa ? withTiming(0, {duration: animationDuration}) : 0;
|
|
|
|
const sub = AppState.addEventListener('change', (state) => {
|
|
if (state === 'inactive' || state === 'background') {
|
|
// Freeze the animation at its current value so the UI thread doesn't
|
|
// race to completion while the app is backgrounded.
|
|
cancelAnimation(translateX);
|
|
} else if (state === 'active') {
|
|
cancelAnimation(translateX);
|
|
translateX.value = latestRef.current.width;
|
|
translateX.value = latestRef.current.shouldAnimate ? withTiming(0, {duration: animationDuration}) : 0;
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
sub.remove();
|
|
const {width: w, shouldAnimate: saBlur} = latestRef.current;
|
|
translateX.value = saBlur ? withTiming(-w, {duration: animationDuration}) : 0;
|
|
};
|
|
}, [translateX, animationDuration]),
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!shouldAnimate) {
|
|
translateX.value = 0;
|
|
}
|
|
}, [translateX, shouldAnimate]);
|
|
|
|
return animatedStyle;
|
|
};
|