animate onboarding screen appear from screen to onboarding by clicking back button

This commit is contained in:
Pablo Velez Vidal 2022-11-24 09:47:12 +01:00
parent fa7a605c8f
commit 0aabcb9918
3 changed files with 64 additions and 34 deletions

View file

@ -1,9 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, StyleSheet} from 'react-native';
import Animated, {useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue} from 'react-native-reanimated';
import React, {useCallback, useEffect} from 'react';
import {View, ListRenderItemInfo, useWindowDimensions, SafeAreaView, ScrollView, StyleSheet, Platform} from 'react-native';
import {Navigation} from 'react-native-navigation';
import Animated, {useAnimatedRef, useAnimatedScrollHandler, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming} from 'react-native-reanimated';
import {storeOnboardingViewedValue} from '@actions/app/global';
import {Screens} from '@app/constants';
@ -36,16 +37,22 @@ const styles = StyleSheet.create({
},
});
const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView);
const Onboarding = ({
theme,
}: OnboardingProps) => {
const {width} = useWindowDimensions();
const {slidesData} = useSlidesData();
const lastSlideIndex = slidesData.length - 1;
const LAST_SLIDE_INDEX = slidesData.length - 1;
const dimensions = useWindowDimensions();
const slidesRef = useAnimatedRef<ScrollView>();
const scrollX = useSharedValue(0);
// used to smothly animate the whole onboarding screen during the appear event scenario (from server screen back to onboarding screen)
const translateX = useSharedValue(dimensions.width);
const currentIndex = useDerivedValue(() => Math.round(scrollX.value / width));
const moveToSlide = useCallback((slideIndexToMove: number) => {
@ -57,9 +64,9 @@ const Onboarding = ({
const nextSlide = useCallback(() => {
const nextSlideIndex = currentIndex.value + 1;
if (slidesRef.current && currentIndex.value < lastSlideIndex) {
if (slidesRef.current && currentIndex.value < LAST_SLIDE_INDEX) {
moveToSlide(nextSlideIndex);
} else if (slidesRef.current && currentIndex.value === lastSlideIndex) {
} else if (slidesRef.current && currentIndex.value === LAST_SLIDE_INDEX) {
signInHandler();
}
}, [currentIndex.value, slidesRef.current, moveToSlide]);
@ -68,7 +75,7 @@ const Onboarding = ({
// mark the onboarding as already viewed
storeOnboardingViewedValue();
goToScreen(Screens.SERVER, '', {theme}, loginAnimationOptions());
goToScreen(Screens.SERVER, '', {animated: true, theme}, loginAnimationOptions());
}, []);
const renderSlide = useCallback(({item, index}: ListRenderItemInfo<OnboardingItem>) => {
@ -79,6 +86,7 @@ const Onboarding = ({
scrollX={scrollX}
index={index}
key={`key-${index.toString()}`}
lastSlideIndex={LAST_SLIDE_INDEX}
/>
);
}, [theme]);
@ -89,15 +97,36 @@ const Onboarding = ({
},
});
useEffect(() => {
const listener = {
componentDidAppear: () => {
translateX.value = 0;
},
componentDidDisappear: () => {
translateX.value = -dimensions.width;
},
};
const unsubscribe = Navigation.events().registerComponentListener(listener, Screens.ONBOARDING);
return () => unsubscribe.remove();
}, [dimensions]);
const transform = useAnimatedStyle(() => {
const duration = Platform.OS === 'android' ? 250 : 350;
return {
transform: [{translateX: withTiming(translateX.value, {duration})}],
};
}, []);
return (
<View
style={styles.onBoardingContainer}
testID='onboarding.screen'
>
<Background theme={theme}/>
<SafeAreaView
<AnimatedSafeArea
style={[styles.scrollContainer, transform]}
key={'onboarding_content'}
style={styles.scrollContainer}
>
<Animated.ScrollView
scrollEventThrottle={16}
@ -123,9 +152,9 @@ const Onboarding = ({
nextSlideHandler={nextSlide}
signInHandler={signInHandler}
scrollX={scrollX}
lastSlideIndex={lastSlideIndex}
lastSlideIndex={LAST_SLIDE_INDEX}
/>
</SafeAreaView>
</AnimatedSafeArea>
</View>
);
};

View file

@ -14,6 +14,7 @@ type Props = {
theme: Theme;
scrollX: Animated.SharedValue<number>;
index: number;
lastSlideIndex: number;
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
@ -57,10 +58,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
}));
const FIRST_SLIDE = 0;
const LAST_SLIDE = 3;
const FIRST_LOAD_ELEMENTS_POSITION = 400;
const SlideItem = ({theme, item, scrollX, index}: Props) => {
const SlideItem = ({theme, item, scrollX, index, lastSlideIndex}: Props) => {
const {width} = useWindowDimensions();
const styles = getStyleSheet(theme);
@ -69,9 +68,9 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => {
*/
const [firstLoad, setFirstLoad] = useState(true);
const initialImagePosition = useSharedValue(FIRST_LOAD_ELEMENTS_POSITION);
const initialTitlePosition = useSharedValue(FIRST_LOAD_ELEMENTS_POSITION);
const initialDescriptionPosition = useSharedValue(FIRST_LOAD_ELEMENTS_POSITION);
const initialImagePosition = useSharedValue(width);
const initialTitlePosition = useSharedValue(width);
const initialDescriptionPosition = useSharedValue(width);
const initialElementsOpacity = useSharedValue(0);
@ -189,7 +188,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => {
style={[
styles.title,
(index === FIRST_SLIDE ? styles.fontFirstTitle : styles.fontTitle),
(index === LAST_SLIDE ? styles.widthLastSlide : undefined),
(index === lastSlideIndex ? styles.widthLastSlide : undefined),
translateTitle,
opacity,
translateFirstTitleOnLoad,
@ -204,7 +203,7 @@ const SlideItem = ({theme, item, scrollX, index}: Props) => {
translateDescription,
opacity,
(index === FIRST_SLIDE && firstLoad ? styles.firstSlideInitialPosition : undefined),
(index === LAST_SLIDE ? styles.widthLastSlide : undefined),
(index === lastSlideIndex ? styles.widthLastSlide : undefined),
translateFirstDescriptionOnLoad,
]}
>

View file

@ -36,6 +36,7 @@ import ServerHeader from './header';
import type {DeepLinkWithData, LaunchProps} from '@typings/launch';
interface ServerProps extends LaunchProps {
animated?: boolean;
closeButtonId?: string;
componentId: string;
theme: Theme;
@ -48,9 +49,24 @@ const defaultServerUrlMessage = {
defaultMessage: 'Please enter a valid server URL',
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
appInfo: {
color: changeOpacity(theme.centerChannelColor, 0.56),
},
flex: {
flex: 1,
},
scrollContainer: {
alignItems: 'center',
height: '100%',
justifyContent: 'center',
},
}));
const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView);
const Server = ({
animated,
closeButtonId,
componentId,
displayName: defaultDisplayName,
@ -63,7 +79,7 @@ const Server = ({
const intl = useIntl();
const managedConfig = useManagedConfig<ManagedConfig>();
const dimensions = useWindowDimensions();
const translateX = useSharedValue(0);
const translateX = useSharedValue(animated ? dimensions.width : 0);
const keyboardAwareRef = useRef<KeyboardAwareScrollView>(null);
const [connecting, setConnecting] = useState(false);
const [displayName, setDisplayName] = useState<string>('');
@ -362,18 +378,4 @@ const Server = ({
);
};
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
appInfo: {
color: changeOpacity(theme.centerChannelColor, 0.56),
},
flex: {
flex: 1,
},
scrollContainer: {
alignItems: 'center',
height: '100%',
justifyContent: 'center',
},
}));
export default Server;