initial migration of everything using reanimated
This commit is contained in:
parent
88ef4fa9ed
commit
12ce554491
4 changed files with 132 additions and 114 deletions
|
|
@ -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()}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue