-
Notifications
You must be signed in to change notification settings - Fork 753
ScreenFooter - add animationType #3994
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
6a8dd58
8e2b631
94729e4
f00b787
b8fe418
0c64821
914a759
677cbb2
6215e6e
72d5982
dc7f16e
9bbe2c5
fe436f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import {useAnimatedKeyboard, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; | ||
| import {StyleSheet, ViewStyle} from 'react-native'; | ||
| import {AnimatedFooterStyleProps, ScreenFooterProps} from './types'; | ||
| import {Constants} from '../../commons/new'; | ||
| import {useEffect, useMemo, useState} from 'react'; | ||
|
|
||
| const androidVersion = Constants.getAndroidVersion(); | ||
| const useAnimatedFooterStyle = ( | ||
| props: AnimatedFooterStyleProps & Pick<ScreenFooterProps, 'keyboardBehavior' | 'visible' | 'isAndroidEdgeToEdge'> | ||
| ) => { | ||
| const { | ||
| animationType: animationTypeProp = 'slide', | ||
| animationDuration: animationDurationProp = 200, | ||
| keyboardBehavior, | ||
| visible, | ||
| isAndroidEdgeToEdge = !!androidVersion && androidVersion >= 35 ? true : undefined | ||
| } = props; | ||
|
|
||
| const animationType = animationDurationProp === 0 ? 'none' : animationTypeProp; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. animationType (a plain JS value) is used inside useAnimatedStyle, but Reanimated worklet closures capture the value at creation time. Since animationType is derived from props and can change, the worklet will reference a stale value. IMO it's not a common case but worth fixing it
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the possible bug?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It reproduces in the screen with |
||
| const animationDuration = animationType === 'none' ? 0 : animationDurationProp; | ||
|
|
||
| const keyboard = useAnimatedKeyboard({ | ||
| isNavigationBarTranslucentAndroid: isAndroidEdgeToEdge, | ||
| isStatusBarTranslucentAndroid: isAndroidEdgeToEdge | ||
| }); | ||
|
|
||
| const [height, setHeight] = useState(0); | ||
| const animatedValue = useSharedValue(animationType === 'fade' && visible ? 1 : 0); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When animationType='slide' and visible=false, the initial animatedValue is 0 — but for slide, hidden state means animatedValue = height. Since height starts at 0, the footer will briefly flash visible until the first layout fires and useEffect updates the value. This is likely a subtle flash-of-content bug on mount.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To fix this we'll probably need a bigger change like I've mentioned in the PR description. |
||
|
|
||
| useEffect(() => { | ||
| if (animationType === 'slide') { | ||
| animatedValue.value = withTiming(visible ? 0 : height, {duration: animationDuration}); | ||
| } else { | ||
| animatedValue.value = withTiming(visible ? 1 : 0, {duration: animationDuration}); | ||
| } | ||
| }, [visible, height, animationDuration, animatedValue, animationType]); | ||
|
|
||
| const animatedStyle = useAnimatedStyle(() => { | ||
| let style: ViewStyle = {}; | ||
| let translateY = 0; | ||
| if (animationType === 'slide') { | ||
| translateY = animatedValue.value; | ||
| } else { | ||
| style = {opacity: animatedValue.value}; | ||
| } | ||
|
|
||
| if (keyboardBehavior === 'sticky' && Constants.isAndroid) { | ||
| translateY += keyboard.height.value; | ||
| } | ||
|
|
||
| if (translateY !== 0) { | ||
| style.transform = [{translateY}]; | ||
| } | ||
|
|
||
| return style; | ||
| }); | ||
|
|
||
| const containerStyle = useMemo(() => { | ||
| return [styles.container, animatedStyle]; | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [keyboardBehavior]); | ||
|
|
||
| return {containerStyle, setHeight}; | ||
| }; | ||
|
|
||
| export default useAnimatedFooterStyle; | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| position: 'absolute', | ||
| bottom: 0, | ||
| left: 0, | ||
| right: 0 | ||
| } | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is minor, but you can remove these two
Viewsand passcontainerStyle={styles.rowContainer}to theSegmentedControlinsteadThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copy-pasted the other parts, technically it should be using
ExampleScreenPresenterall over this