diff --git a/app/screens/onboarding/index.tsx b/app/screens/onboarding/index.tsx index bf4287f41..7fe18e8e2 100644 --- a/app/screens/onboarding/index.tsx +++ b/app/screens/onboarding/index.tsx @@ -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(); 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) => { @@ -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 ( - - + ); }; diff --git a/app/screens/onboarding/slide.tsx b/app/screens/onboarding/slide.tsx index 9f5a96408..5869883fc 100644 --- a/app/screens/onboarding/slide.tsx +++ b/app/screens/onboarding/slide.tsx @@ -14,6 +14,7 @@ type Props = { theme: Theme; scrollX: Animated.SharedValue; 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, ]} > diff --git a/app/screens/server/index.tsx b/app/screens/server/index.tsx index ec6a44c99..943debf86 100644 --- a/app/screens/server/index.tsx +++ b/app/screens/server/index.tsx @@ -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(); const dimensions = useWindowDimensions(); - const translateX = useSharedValue(0); + const translateX = useSharedValue(animated ? dimensions.width : 0); const keyboardAwareRef = useRef(null); const [connecting, setConnecting] = useState(false); const [displayName, setDisplayName] = useState(''); @@ -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;