animate final buttons and modify paginator animation

This commit is contained in:
Pablo Velez Vidal 2022-11-02 23:47:05 +01:00
parent 12ce554491
commit 5522b20084
3 changed files with 108 additions and 66 deletions

View file

@ -4,7 +4,7 @@
import React, {useEffect} from 'react';
import {Pressable, useWindowDimensions, View} from 'react-native';
import Button from 'react-native-button';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import Animated, {interpolate, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import CompassIcon from '@app/components/compass_icon';
import FormattedText from '@app/components/formatted_text';
@ -14,9 +14,11 @@ import {makeStyleSheetFromTheme} from '@utils/theme';
type Props = {
theme: Theme;
isLastSlide: boolean;
lastSlideIndex: number;
nextSlideHandler: any;
signInHandler: any;
currentIndex: number;
scrollX: Animated.SharedValue<number>;
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
@ -38,29 +40,40 @@ const FooterButtons = ({
nextSlideHandler,
signInHandler,
isLastSlide,
lastSlideIndex,
currentIndex,
scrollX,
}: Props) => {
const {width} = useWindowDimensions();
const styles = getStyleSheet(theme);
const BUTTON_SIZE = 80;
const scaledWidth = useSharedValue(80);
// keep in mind penultimate and ultimate slides to run buttons animations
const isPenultimateSlide = currentIndex === (lastSlideIndex - 1);
const needToAnimate = isLastSlide || isPenultimateSlide;
const transform = useAnimatedStyle(() => {
return {
width: scaledWidth.value,
};
const resizeStyle = useAnimatedStyle(() => {
const interpolatedWidth = interpolate(
scrollX.value,
[(currentIndex - 1) * width, currentIndex * width, (currentIndex + 1) * width],
[BUTTON_SIZE, isLastSlide ? width * 0.8 : BUTTON_SIZE, width * 0.8],
);
return {width: needToAnimate ? interpolatedWidth : BUTTON_SIZE};
});
useEffect(() => {
console.log('** footer buttons rerender', isLastSlide);
}, []);
const opacityTextStyle = useAnimatedStyle(() => {
const interpolatedScale = interpolate(
scrollX.value,
[(currentIndex - 1) * width, currentIndex * width, (currentIndex + 1) * width],
[isPenultimateSlide ? 1 : 0, 1, 0],
);
useEffect(() => {
console.log('** footer buttons rerender', isLastSlide);
scaledWidth.value = withTiming(isLastSlide ? ((width * 80) / 100) : 80, {duration: 100});
}, [isLastSlide]);
return {opacity: needToAnimate ? interpolatedScale : 1};
});
let mainButtonText = (
<View style={{flexDirection: 'row'}}>
<Animated.View style={[{flexDirection: 'row'}, opacityTextStyle]}>
<FormattedText
id='mobile.onboarding.next'
defaultMessage='Next'
@ -70,18 +83,20 @@ const FooterButtons = ({
name='arrow-forward-ios'
style={styles.rowIcon}
/>
</View>
</Animated.View>
);
let mainButtonAction = nextSlideHandler;
if (isLastSlide) {
mainButtonText = (
<FormattedText
id='mobile.onboarding.sign_in_to_get_started'
defaultMessage='Sign in to get started'
style={buttonTextStyle(theme, 's', 'primary', 'default')}
/>
<Animated.View style={[{flexDirection: 'row'}, opacityTextStyle]}>
<FormattedText
id='mobile.onboarding.sign_in_to_get_started'
defaultMessage='Sign in to get started'
style={buttonTextStyle(theme, 's', 'primary', 'default')}
/>
</Animated.View>
);
mainButtonAction = signInHandler;
}
@ -91,7 +106,7 @@ const FooterButtons = ({
<AnimatedButton
testID='mobile.onboaring.next'
onPress={() => mainButtonAction()}
style={[styles.button, buttonBackgroundStyle(theme, 'm', 'primary', 'default'), transform]}
style={[styles.button, buttonBackgroundStyle(theme, 'm', 'primary', 'default'), resizeStyle]}
>
{mainButtonText}
</AnimatedButton>

View file

@ -51,12 +51,12 @@ const Onboarding = ({
setIsLastSlide(!isLastSlide);
};
const transform = useAnimatedStyle(() => {
const duration = Platform.OS === 'android' ? 250 : 350;
return {
transform: [{translateX: withTiming(scrollX.value, {duration})}],
};
}, []);
// const transform = useAnimatedStyle(() => {
// const duration = Platform.OS === 'android' ? 250 : 350;
// return {
// transform: [{translateX: withTiming(scrollX.value, {duration})}],
// };
// }, []);
const renderSlide = useCallback(({item, index}: ListRenderItemInfo<any>) => {
return (
@ -76,7 +76,7 @@ const Onboarding = ({
const handleScroll = useAnimatedScrollHandler(({contentOffset: {x}}) => {
const calculatedIndex = Math.round(x / width);
scrollX.value = x;
if (calculatedIndex !== currentIndex.value) {
currentIndex.value = calculatedIndex;
runOnJS(toogleIsLastSlideValue)(calculatedIndex === lastSlideIndex);
@ -91,7 +91,7 @@ const Onboarding = ({
<Background theme={theme}/>
<AnimatedSafeArea
key={'onboarding_content'}
style={[styles.scrollContainer, transform]}
style={[styles.scrollContainer]}
>
<Animated.ScrollView
scrollEventThrottle={16}
@ -117,6 +117,9 @@ const Onboarding = ({
isLastSlide={isLastSlide}
nextSlideHandler={nextSlide}
signInHandler={signInHandler}
currentIndex={currentIndex.value}
scrollX={scrollX}
lastSlideIndex={lastSlideIndex}
/>
</AnimatedSafeArea>
</View>

View file

@ -3,7 +3,7 @@
import React from 'react';
import {View, useWindowDimensions, TouchableOpacity} from 'react-native';
import Animated from 'react-native-reanimated';
import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated';
import {makeStyleSheetFromTheme} from '@utils/theme';
@ -23,6 +23,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
backgroundColor: theme.buttonBg,
marginHorizontal: DOT_SIZE / 2,
width: DOT_SIZE / 2,
opacity: 0.25,
},
outerDot: {
height: DOT_SIZE,
@ -32,6 +33,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
marginTop: -4,
position: 'absolute',
width: DOT_SIZE,
opacity: 0.15,
},
button: {
marginTop: 5,
@ -44,47 +46,19 @@ const Paginator = ({
scrollX,
moveToSlide,
}: Props) => {
const styles = getStyleSheet(theme);
const {width} = useWindowDimensions();
return (
<View style={{flexDirection: 'column', height: 10}}>
<View style={{flexDirection: 'row', height: 5}}>
{data.map((item: any, i: number) => {
// const inputRange = [(i - 1) * width, i * width, (i + 1) * width];
// const opacity = scrollX.interpolate({
// inputRange,
// outputRange: [0.25, 1, 0.25],
// extrapolate: 'clamp',
// });
// const opacityOuterDot = scrollX.interpolate({
// inputRange,
// outputRange: [0, 0.15, 0],
// extrapolate: 'clamp',
// });
return (
<TouchableOpacity
onPress={() => moveToSlide(i)}
<Dot
item={item}
key={item.id}
>
<Animated.View
// style={[styles.outerDot, {
// opacity: opacityOuterDot,
// }]}
key={'outer-' + item.id + i.toString()}
style={[styles.outerDot]}
/>
<Animated.View
// style={[styles.dot, {
// opacity,
// }]}
key={item.id + i.toString()}
style={[styles.dot]}
/>
</TouchableOpacity>
theme={theme}
moveToSlide={moveToSlide}
index={i}
scrollX={scrollX}
/>
);
})}
</View>
@ -92,4 +66,54 @@ const Paginator = ({
);
};
type DotProps = {
item: any;
index: number;
scrollX: Animated.SharedValue<number>;
theme: Theme;
moveToSlide: any;
};
const Dot = ({item, index, scrollX, theme, moveToSlide}: DotProps) => {
const {width} = useWindowDimensions();
const styles = getStyleSheet(theme);
const inputRange = [(index - 1) * width, index * width, (index + 1) * width];
const dotOpacity = useAnimatedStyle(() => {
const opacity = interpolate(
scrollX.value,
inputRange,
[0.25, 1, 0.25],
);
return {opacity};
});
const outerDotOpacity = useAnimatedStyle(() => {
const opacity = interpolate(
scrollX.value,
inputRange,
[0, 0.15, 0],
);
return {opacity};
});
return (
<TouchableOpacity
onPress={() => moveToSlide(index)}
key={item.id}
>
<Animated.View
style={[styles.outerDot, outerDotOpacity]}
key={'outer-' + item.id + index.toString()}
/>
<Animated.View
style={[styles.dot, dotOpacity]}
key={item.id + index.toString()}
/>
</TouchableOpacity>
);
};
export default Paginator;