initial migration of everything using reanimated

This commit is contained in:
Pablo Velez Vidal 2022-11-02 17:57:57 +01:00
parent 88ef4fa9ed
commit 12ce554491
4 changed files with 132 additions and 114 deletions

View file

@ -2,8 +2,9 @@
// See LICENSE.txt for license information.
import React, {useEffect} from 'react';
import {useWindowDimensions, View} from 'react-native';
import {Pressable, useWindowDimensions, View} from 'react-native';
import Button from 'react-native-button';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import CompassIcon from '@app/components/compass_icon';
import FormattedText from '@app/components/formatted_text';
@ -15,7 +16,6 @@ type Props = {
isLastSlide: boolean;
nextSlideHandler: any;
signInHandler: any;
scrollX: any;
};
@ -31,21 +31,32 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
},
}));
const AnimatedButton = Animated.createAnimatedComponent(Pressable);
const FooterButtons = ({
theme,
nextSlideHandler,
signInHandler,
isLastSlide,
scrollX,
}: Props) => {
const {width} = useWindowDimensions();
const styles = getStyleSheet(theme);
const inputRange = [-width, 0, width];
const scaledWidth = useSharedValue(80);
const transform = useAnimatedStyle(() => {
return {
width: scaledWidth.value,
};
});
useEffect(() => {
if (isLastSlide) {
console.log('is last slide');
}
console.log('** footer buttons rerender', isLastSlide);
}, []);
useEffect(() => {
console.log('** footer buttons rerender', isLastSlide);
scaledWidth.value = withTiming(isLastSlide ? ((width * 80) / 100) : 80, {duration: 100});
}, [isLastSlide]);
let mainButtonText = (
@ -77,13 +88,13 @@ const FooterButtons = ({
return (
<View style={{flexDirection: 'column', height: 150, marginTop: 15, width: '100%', marginHorizontal: 10, alignItems: 'center'}}>
<Button
<AnimatedButton
testID='mobile.onboaring.next'
onPress={() => mainButtonAction()}
containerStyle={[styles.button, buttonBackgroundStyle(theme, 'm', 'primary', 'default'), {width: isLastSlide ? '90%' : 80}]}
style={[styles.button, buttonBackgroundStyle(theme, 'm', 'primary', 'default'), transform]}
>
{mainButtonText}
</Button>
</AnimatedButton>
<Button
testID='mobile.onboaring.sign_in'
onPress={() => signInHandler()}

View file

@ -1,12 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useRef, useState} from 'react';
import {Platform, View, Animated, ListRenderItemInfo} from 'react-native';
import {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import {SafeAreaView} from 'react-native-safe-area-context';
import React, {useCallback, useState} from 'react';
import {View, ListRenderItemInfo, useWindowDimensions, Platform, SafeAreaView} from 'react-native';
import Animated, {runOnJS, useAnimatedRef, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import {generateId} from '@app/utils/general';
import Background from '@screens/background';
import {makeStyleSheetFromTheme} from '@utils/theme';
@ -20,41 +18,43 @@ import type {LaunchProps} from '@typings/launch';
interface OnboardingProps extends LaunchProps {
theme: Theme;
}
const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView);
const Onboarding = ({
theme,
}: OnboardingProps) => {
const translateX = useSharedValue(0);
const {width} = useWindowDimensions();
const styles = getStyleSheet(theme);
const [currentIndex, setCurrentIndex] = useState(0);
const [isLastSlide, setIsLastSlide] = useState(false);
const lastSlideIndex = slidesData.length - 1;
const slidesRef = useRef(null);
const slidesRef = useAnimatedRef<Animated.ScrollView>();
const currentIndex = useSharedValue(0);
const scrollX = useSharedValue(0);
const nextSlide = () => {
const nextSlideIndex = currentIndex + 1;
if (slidesRef.current && currentIndex < lastSlideIndex) {
const nextSlideIndex = currentIndex.value + 1;
if (slidesRef.current && currentIndex.value < lastSlideIndex) {
moveToSlide(nextSlideIndex);
}
};
const moveToSlide = (slideIndexToMove: number) => {
setIsLastSlide(slideIndexToMove === lastSlideIndex);
setCurrentIndex(slideIndexToMove);
currentIndex.value = slideIndexToMove;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
slidesRef?.current?.scrollToIndex({index: slideIndexToMove});
slidesRef?.current?.scrollTo({x: (slideIndexToMove * width), animated: true});
};
const signInHandler = () => {
console.log('sign in handler');
// temporal validation
setIsLastSlide(!isLastSlide);
};
const transform = useAnimatedStyle(() => {
const duration = Platform.OS === 'android' ? 250 : 350;
return {
transform: [{translateX: withTiming(translateX.value, {duration})}],
transform: [{translateX: withTiming(scrollX.value, {duration})}],
};
}, []);
@ -65,18 +65,23 @@ const Onboarding = ({
theme={theme}
scrollX={scrollX}
index={index}
key={item.id}
/>
);
}, []);
const scrollX = useRef(new Animated.Value(0)).current;
const toogleIsLastSlideValue = (isLast: boolean) => {
setIsLastSlide(isLast);
};
const viewableItemsChanged = useRef(({viewableItems}: any) => {
setCurrentIndex(viewableItems[0].index);
setIsLastSlide(viewableItems[0].index === lastSlideIndex);
}).current;
const handleScroll = useAnimatedScrollHandler(({contentOffset: {x}}) => {
const calculatedIndex = Math.round(x / width);
const viewConfig = useRef({viewAreaCoveragePercentThreshold: 50}).current;
if (calculatedIndex !== currentIndex.value) {
currentIndex.value = calculatedIndex;
runOnJS(toogleIsLastSlideValue)(calculatedIndex === lastSlideIndex);
}
});
return (
<View
@ -88,37 +93,32 @@ const Onboarding = ({
key={'onboarding_content'}
style={[styles.scrollContainer, transform]}
>
<Animated.FlatList
keyExtractor={(item) => item.id}
data={slidesData}
renderItem={renderSlide}
listKey={generateId()}
<Animated.ScrollView
scrollEventThrottle={16}
horizontal={true}
showsHorizontalScrollIndicator={false}
pagingEnabled={true}
bounces={false}
onScroll={Animated.event([{nativeEvent: {contentOffset: {x: scrollX}}}], {
useNativeDriver: true,
})}
onViewableItemsChanged={viewableItemsChanged}
viewabilityConfig={viewConfig}
scrollEventThrottle={32}
onMomentumScrollEnd={handleScroll}
ref={slidesRef}
>
{slidesData.map((item, index) => {
return renderSlide({item, index} as ListRenderItemInfo<any>);
})}
</Animated.ScrollView>
<Paginator
data={slidesData}
theme={theme}
scrollX={scrollX}
moveToSlide={moveToSlide}
/>
<FooterButtons
theme={theme}
isLastSlide={isLastSlide}
nextSlideHandler={nextSlide}
signInHandler={signInHandler}
/>
</AnimatedSafeArea>
<Paginator
data={slidesData}
theme={theme}
scrollX={scrollX}
moveToSlide={moveToSlide}
/>
<FooterButtons
theme={theme}
isLastSlide={isLastSlide}
nextSlideHandler={nextSlide}
signInHandler={signInHandler}
scrollX={scrollX}
/>
</View>
);
};

View file

@ -2,14 +2,15 @@
// See LICENSE.txt for license information.
import React from 'react';
import {View, Animated, useWindowDimensions, TouchableOpacity} from 'react-native';
import {View, useWindowDimensions, TouchableOpacity} from 'react-native';
import Animated from 'react-native-reanimated';
import {makeStyleSheetFromTheme} from '@utils/theme';
type Props = {
data: any;
theme: Theme;
scrollX: any;
scrollX: Animated.SharedValue<number>;
moveToSlide: any;
};
@ -50,19 +51,19 @@ const Paginator = ({
<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 inputRange = [(i - 1) * width, i * width, (i + 1) * width];
const opacity = scrollX.interpolate({
inputRange,
outputRange: [0.25, 1, 0.25],
extrapolate: 'clamp',
});
// 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',
});
// const opacityOuterDot = scrollX.interpolate({
// inputRange,
// outputRange: [0, 0.15, 0],
// extrapolate: 'clamp',
// });
return (
<TouchableOpacity
@ -70,16 +71,18 @@ const Paginator = ({
key={item.id}
>
<Animated.View
style={[styles.outerDot, {
opacity: opacityOuterDot,
}]}
// style={[styles.outerDot, {
// opacity: opacityOuterDot,
// }]}
key={'outer-' + item.id + i.toString()}
style={[styles.outerDot]}
/>
<Animated.View
style={[styles.dot, {
opacity,
}]}
// style={[styles.dot, {
// opacity,
// }]}
key={item.id + i.toString()}
style={[styles.dot]}
/>
</TouchableOpacity>
);

View file

@ -2,7 +2,8 @@
// See LICENSE.txt for license information.
import React from 'react';
import {Animated, View, useWindowDimensions} from 'react-native';
import {View, useWindowDimensions} from 'react-native';
import Animated from 'react-native-reanimated';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
@ -10,7 +11,7 @@ import {typography} from '@utils/typography';
type Props = {
item: any;
theme: Theme;
scrollX: any;
scrollX: Animated.SharedValue<number>;
index: number;
};
@ -55,62 +56,65 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => {
const styles = getStyleSheet(theme);
const SvgImg = item.image;
const inputRange = [(index - 1) * width, index * width, (index + 1) * width];
// const inputRange = [(index - 1) * width, index * width, (index + 1) * width];
const translateImage = scrollX.interpolate({
inputRange,
outputRange: [width * 2, 0, -width * 2],
});
// const translateImage = scrollX.interpolate({
// inputRange,
// outputRange: [width * 2, 0, -width * 2],
// });
const translateTitle = scrollX.interpolate({
inputRange,
outputRange: [width * 0.6, 0, -width * 0.6],
});
// const translateTitle = scrollX.interpolate({
// inputRange,
// outputRange: [width * 0.6, 0, -width * 0.6],
// });
const translateDescription = scrollX.interpolate({
inputRange,
outputRange: [width * 0.2, 0, -width * 0.2],
});
// const translateDescription = scrollX.interpolate({
// inputRange,
// outputRange: [width * 0.2, 0, -width * 0.2],
// });
const opacity = scrollX.interpolate({
inputRange,
outputRange: [0.2, 1, 0.2],
});
// const opacity = scrollX.interpolate({
// inputRange,
// outputRange: [0.2, 1, 0.2],
// });
return (
<View style={[styles.itemContainer, {width}]}>
<Animated.View
style={[{
transform: [{
translateX: translateImage,
}],
}]}
// style={[{
// transform: [{
// translateX: translateImage,
// }],
// }]}
>
<SvgImg
style={[styles.image, {
width,
resizeMode: 'contain',
}]}
// style={[styles.image, {
// width,
// resizeMode: 'contain',
// }]}
style={[styles.image]}
/>
</Animated.View>
<View style={{flex: 0.3}}>
<Animated.Text
style={[styles.title, (index === 0 ? styles.fontFirstTitle : styles.fontTitle), {
transform: [{
translateX: translateTitle,
}],
opacity,
}]}
// style={[styles.title, (index === 0 ? styles.fontFirstTitle : styles.fontTitle), {
// transform: [{
// translateX: translateTitle,
// }],
// opacity,
// }]}
style={[styles.title, (index === 0 ? styles.fontFirstTitle : styles.fontTitle)]}
>
{item.title}
</Animated.Text>
<Animated.Text
style={[styles.description, {
transform: [{
translateX: translateDescription,
}],
opacity,
}]}
// style={[styles.description, {
// transform: [{
// translateX: translateDescription,
// }],
// opacity,
// }]}
style={[styles.description]}
>
{item.description}
</Animated.Text>