animate final buttons and modify paginator animation
This commit is contained in:
parent
12ce554491
commit
5522b20084
3 changed files with 108 additions and 66 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue