Skip to content

Commit

Permalink
[RNMobile] Use Reanimated in bottom sheet height animation (#52563)
Browse files Browse the repository at this point in the history
* Expose max height properties in `BottomSheetProvider`

* Animate bottom sheet's height with Reanimated

Pass `currentHeight` in bottom sheet navigation context

* Use pixel value when setting fullscreen height

We need to pass pixel values in order to animate the height with Reanimated.

* Rename `heightRef` to `maxHeight`

* Re-enable `exhaustive-deps` lint rule in `BottomSheetNavigationContainer`

* Avoid setting height using debounce

* Add test ID to navigation container component

* Mock Reanimated's `now` function

* Update test cases related to bottom sheet height animation

* Update test snapshots
  • Loading branch information
fluiddot authored Jul 14, 2023
1 parent 470af37 commit 0411df1
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 197 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createContext } from '@wordpress/element';
// Navigation context in BottomSheet is necessary for controlling the
// height of navigation container.
export const BottomSheetNavigationContext = createContext( {
currentHeight: 1,
currentHeight: { value: 0 },
setHeight: () => {},
} );

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
/**
* External dependencies
*/
import { View, Easing } from 'react-native';
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Animated, {
Easing,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';

/**
* WordPress dependencies
*/
import {
useState,
useContext,
useMemo,
useCallback,
Expand All @@ -23,11 +27,11 @@ import { usePreferredColorSchemeStyle } from '@wordpress/compose';
/**
* Internal dependencies
*/
import { performLayoutAnimation } from '../../layout-animation';
import {
BottomSheetNavigationContext,
BottomSheetNavigationProvider,
} from './bottom-sheet-navigation-context';
import { BottomSheetContext } from '../bottom-sheet-context';

import styles from './styles.scss';

Expand Down Expand Up @@ -57,67 +61,73 @@ const options = {
cardStyleInterpolator: fadeConfig,
};

const ANIMATION_DURATION = 190;
const HEIGHT_ANIMATION_DURATION = 300;
const DEFAULT_HEIGHT = 1;

function BottomSheetNavigationContainer( {
children,
animate,
main,
theme,
style,
testID,
} ) {
const Stack = useRef( createStackNavigator() ).current;
const context = useContext( BottomSheetNavigationContext );
const [ currentHeight, setCurrentHeight ] = useState(
context.currentHeight || 1
const navigationContext = useContext( BottomSheetNavigationContext );
const { maxHeight: sheetMaxHeight, isMaxHeightSet: isSheetMaxHeightSet } =
useContext( BottomSheetContext );
const currentHeight = useSharedValue(
navigationContext.currentHeight?.value || DEFAULT_HEIGHT
);

const backgroundStyle = usePreferredColorSchemeStyle(
styles.background,
styles.backgroundDark
);

const _theme = theme || {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: backgroundStyle.backgroundColor,
},
};
const defaultTheme = useMemo(
() => ( {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: backgroundStyle.backgroundColor,
},
} ),
[ backgroundStyle.backgroundColor ]
);
const _theme = theme || defaultTheme;

const setHeight = useCallback(
( height ) => {
// The screen is fullHeight.
if (
typeof height === 'string' &&
typeof height !== typeof currentHeight
height > DEFAULT_HEIGHT &&
Math.round( height ) !== Math.round( currentHeight.value )
) {
performLayoutAnimation( ANIMATION_DURATION );
setCurrentHeight( height );

return;
}

if (
height > 1 &&
Math.round( height ) !== Math.round( currentHeight )
) {
if ( currentHeight === 1 ) {
setCurrentHeight( height );
} else if ( animate ) {
performLayoutAnimation( ANIMATION_DURATION );
setCurrentHeight( height );
// If max height is set in the bottom sheet, we clamp
// the new height using that value.
const newHeight = isSheetMaxHeightSet
? Math.min( sheetMaxHeight, height )
: height;
const shouldAnimate =
animate && currentHeight.value !== DEFAULT_HEIGHT;

if ( shouldAnimate ) {
currentHeight.value = withTiming( newHeight, {
duration: HEIGHT_ANIMATION_DURATION,
easing: Easing.out( Easing.cubic ),
} );
} else {
setCurrentHeight( height );
currentHeight.value = newHeight;
}
}
},
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
[ currentHeight ]
[ animate, currentHeight, isSheetMaxHeightSet, sheetMaxHeight ]
);

const animatedStyles = useAnimatedStyle( () => ( {
height: currentHeight.value,
} ) );

const screens = useMemo( () => {
return Children.map( children, ( child ) => {
let screen = child;
Expand All @@ -136,19 +146,16 @@ function BottomSheetNavigationContainer( {
/>
);
} );
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ children ] );
}, [ children, main ] );

return useMemo( () => {
return (
<View style={ [ style, { height: currentHeight } ] }>
<Animated.View
style={ [ style, animatedStyles ] }
testID={ testID }
>
<BottomSheetNavigationProvider
value={ {
setHeight,
currentHeight,
} }
value={ { setHeight, currentHeight } }
>
{ main ? (
<NavigationContainer theme={ _theme }>
Expand All @@ -168,12 +175,18 @@ function BottomSheetNavigationContainer( {
</Stack.Navigator>
) }
</BottomSheetNavigationProvider>
</View>
</Animated.View>
);
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ currentHeight, _theme ] );
}, [
_theme,
animatedStyles,
currentHeight,
main,
screens,
setHeight,
style,
testID,
] );
}

export default BottomSheetNavigationContainer;
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import {
useNavigation,
useFocusEffect,
} from '@react-navigation/native';
import { View, ScrollView, TouchableHighlight } from 'react-native';
import {
ScrollView,
TouchableHighlight,
useWindowDimensions,
View,
} from 'react-native';

/**
* WordPress dependencies
*/
import { BottomSheetContext } from '@wordpress/components';
import { debounce } from '@wordpress/compose';
import { useRef, useCallback, useContext, useMemo } from '@wordpress/element';

/**
Expand All @@ -29,7 +33,7 @@ const BottomSheetNavigationScreen = ( {
name,
} ) => {
const navigation = useNavigation();
const heightRef = useRef( { maxHeight: 0 } );
const maxHeight = useRef( 0 );
const isFocused = useIsFocused();
const {
onHandleHardwareButtonPress,
Expand All @@ -38,16 +42,10 @@ const BottomSheetNavigationScreen = ( {
listProps,
safeAreaBottomInset,
} = useContext( BottomSheetContext );
const { height: windowHeight } = useWindowDimensions();

const { setHeight } = useContext( BottomSheetNavigationContext );

// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
const setHeightDebounce = useCallback( debounce( setHeight, 10 ), [
setHeight,
] );

useFocusEffect(
useCallback( () => {
onHandleHardwareButtonPress( () => {
Expand Down Expand Up @@ -81,28 +79,24 @@ const BottomSheetNavigationScreen = ( {
useFocusEffect(
useCallback( () => {
if ( fullScreen ) {
setHeight( '100%' );
setHeight( windowHeight );
setIsFullScreen( true );
} else if ( heightRef.current.maxHeight !== 0 ) {
} else if ( maxHeight.current !== 0 ) {
setIsFullScreen( false );
setHeight( heightRef.current.maxHeight );
setHeight( maxHeight.current );
}
return () => {};
// Disable reason: deferring this refactor to the native team.
// see https://github.com/WordPress/gutenberg/pull/41166
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ setHeight ] )
}, [ fullScreen, setHeight, setIsFullScreen, windowHeight ] )
);

const onLayout = ( { nativeEvent } ) => {
if ( fullScreen ) {
return;
}
const { height } = nativeEvent.layout;

if ( heightRef.current.maxHeight !== height && isFocused ) {
heightRef.current.maxHeight = height;
setHeightDebounce( height );
if ( maxHeight.current !== height && isFocused ) {
maxHeight.current = height;
setHeight( height );
}
};

Expand Down
Loading

0 comments on commit 0411df1

Please sign in to comment.