mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
Fix: onboarding carousel without screen jumpiness (#2825)
* Don't check for prev crashes or sentinel files on a fresh install * Make sure we're not accidentally creating a new legacy store on every install * Revert * Add splash screen, preload images, show onboarding as react nav modal * Fix e2e tests
This commit is contained in:
committed by
GitHub
parent
6dabe7ba5f
commit
fc69a5a456
@@ -7,12 +7,6 @@ import {
|
||||
const VISIBILITY_TIMEOUT = 10_000;
|
||||
|
||||
export default async function closeOnboarding( ) {
|
||||
const loginText = element( by.id( "use-iNaturalist-intro-text" ) );
|
||||
await waitFor( loginText ).toExist().withTimeout( VISIBILITY_TIMEOUT );
|
||||
// If we can see MyObs, we don't need to close the onboarding
|
||||
if ( loginText.visible ) {
|
||||
return Promise.resolve( );
|
||||
}
|
||||
const closeOnboardingButton = element(
|
||||
by.label( "Close" ).withAncestor( by.id( "OnboardingCarousel" ) )
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import AddObsModal from "components/AddObsModal/AddObsModal.tsx";
|
||||
import OnboardingCarouselModal from "components/Onboarding/OnboardingCarouselModal";
|
||||
import {
|
||||
HeaderUser,
|
||||
Heading2,
|
||||
@@ -14,7 +13,6 @@ import Arrow from "images/svg/curved_arrow_down.svg";
|
||||
import type { Node } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { Pressable } from "react-native";
|
||||
import { useOnboardingShown } from "sharedHelpers/installData.ts";
|
||||
import { useTranslation } from "sharedHooks";
|
||||
import useStore from "stores/useStore";
|
||||
|
||||
@@ -26,7 +24,6 @@ interface Props {
|
||||
const MyObservationsEmptySimple = ( { currentUser, isConnected }: Props ): Node => {
|
||||
const { t } = useTranslation();
|
||||
const navigation = useNavigation();
|
||||
const [onboardingShown, setOnboardingShown] = useOnboardingShown();
|
||||
const [showModal, setShowModal] = useState( false );
|
||||
const resetObservationFlowSlice = useStore( state => state.resetObservationFlowSlice );
|
||||
const navAndCloseModal = ( screen, params ) => {
|
||||
@@ -43,10 +40,6 @@ const MyObservationsEmptySimple = ( { currentUser, isConnected }: Props ): Node
|
||||
|
||||
return (
|
||||
<ViewWrapper>
|
||||
<OnboardingCarouselModal
|
||||
showModal={!onboardingShown}
|
||||
closeModal={() => setOnboardingShown( true )}
|
||||
/>
|
||||
{!!currentUser && (
|
||||
<View className="flex-start ml-[18px] mt-[26px]">
|
||||
<HeaderUser user={currentUser} isConnected={isConnected} />
|
||||
|
||||
@@ -7,10 +7,13 @@ import {
|
||||
INatIconButton,
|
||||
ViewWrapper
|
||||
} from "components/SharedComponents";
|
||||
import { ImageBackground } from "components/styledComponents";
|
||||
import INatLogo from "images/svg/inat_logo_onboarding.svg";
|
||||
import OnBoardingIcon2 from "images/svg/onboarding_icon_2.svg";
|
||||
import OnBoardingIcon3 from "images/svg/onboarding_icon_3.svg";
|
||||
import React, {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
@@ -24,6 +27,7 @@ import {
|
||||
import AnimatedDotsCarousel from "react-native-animated-dots-carousel";
|
||||
import Animated, { interpolate, useAnimatedStyle, useSharedValue } from "react-native-reanimated";
|
||||
import Carousel from "react-native-reanimated-carousel";
|
||||
import { useOnboardingShown } from "sharedHelpers/installData.ts";
|
||||
import colors from "styles/tailwindColors";
|
||||
|
||||
const SlideItem = props => {
|
||||
@@ -60,29 +64,54 @@ const SlideItem = props => {
|
||||
);
|
||||
};
|
||||
|
||||
const OnboardingCarousel = ( { closeModal } ) => {
|
||||
const OnboardingCarousel = ( ) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [onboardingShown, setOnboardingShown] = useOnboardingShown();
|
||||
const { width } = useWindowDimensions();
|
||||
const { t } = useTranslation( );
|
||||
const carouselRef = useRef( null );
|
||||
const progress = useSharedValue( 0 );
|
||||
const [currentIndex, setCurrentIndex] = useState( 0 );
|
||||
const [imagesLoaded, setImagesLoaded] = useState( false );
|
||||
|
||||
const closeModal = () => setOnboardingShown( true );
|
||||
|
||||
const paginationColor = colors.white;
|
||||
const ONBOARDING_SLIDES = [
|
||||
const backgroundAnimation1 = useAnimatedStyle( () => {
|
||||
const opacity = interpolate(
|
||||
progress.value,
|
||||
[-1, 0, 1], // Fade in/out around current index
|
||||
[0, 1, 0] // Opacity transitions
|
||||
);
|
||||
return { opacity };
|
||||
} );
|
||||
|
||||
const backgroundAnimation2 = useAnimatedStyle( () => {
|
||||
const opacity = interpolate(
|
||||
progress.value,
|
||||
[0, 1, 2], // Fade in/out around current index
|
||||
[0, 1, 0] // Opacity transitions
|
||||
);
|
||||
return { opacity };
|
||||
} );
|
||||
|
||||
const backgroundAnimation3 = useAnimatedStyle( () => {
|
||||
const opacity = interpolate(
|
||||
progress.value,
|
||||
[1, 2, 3], // Fade in/out around current index
|
||||
[0, 1, 0] // Opacity transitions
|
||||
);
|
||||
return { opacity };
|
||||
} );
|
||||
|
||||
const ONBOARDING_SLIDES = useMemo( ( ) => ( [
|
||||
{
|
||||
icon: null,
|
||||
iconProps: { width: 70, height: 70 },
|
||||
title: t( "Identify-species-anywhere" ),
|
||||
text: t( "Get-an-instant-ID-of-any-plant-animal-fungus" ),
|
||||
background: require( "images/background/karsten-winegeart-RAgWH6ldps0-unsplash-cropped.jpg" ),
|
||||
backgroundAnimation: useAnimatedStyle( () => {
|
||||
const opacity = interpolate(
|
||||
progress.value,
|
||||
[-1, 0, 1], // Fade in/out around current index
|
||||
[0, 1, 0] // Opacity transitions
|
||||
);
|
||||
return { opacity };
|
||||
} )
|
||||
backgroundAnimation: backgroundAnimation1
|
||||
},
|
||||
{
|
||||
icon: OnBoardingIcon2,
|
||||
@@ -90,14 +119,7 @@ const OnboardingCarousel = ( { closeModal } ) => {
|
||||
title: t( "Connect-with-expert-naturalists" ),
|
||||
text: t( "Experts-help-verify-and-improve-IDs" ),
|
||||
background: require( "images/background/shane-rounce-DNkoNXQti3c-unsplash.jpg" ),
|
||||
backgroundAnimation: useAnimatedStyle( () => {
|
||||
const opacity = interpolate(
|
||||
progress.value,
|
||||
[0, 1, 2], // Fade in/out around current index
|
||||
[0, 1, 0] // Opacity transitions
|
||||
);
|
||||
return { opacity };
|
||||
} )
|
||||
backgroundAnimation: backgroundAnimation2
|
||||
},
|
||||
{
|
||||
icon: OnBoardingIcon3,
|
||||
@@ -105,16 +127,9 @@ const OnboardingCarousel = ( { closeModal } ) => {
|
||||
title: t( "Help-protect-species" ),
|
||||
text: t( "Verified-IDs-are-used-for-science-and-conservation" ),
|
||||
background: require( "images/background/sk-yeong-cXpdNdqp7eY-unsplash.jpg" ),
|
||||
backgroundAnimation: useAnimatedStyle( () => {
|
||||
const opacity = interpolate(
|
||||
progress.value,
|
||||
[1, 2, 3], // Fade in/out around current index
|
||||
[0, 1, 0] // Opacity transitions
|
||||
);
|
||||
return { opacity };
|
||||
} )
|
||||
backgroundAnimation: backgroundAnimation3
|
||||
}
|
||||
];
|
||||
] ), [backgroundAnimation1, backgroundAnimation2, backgroundAnimation3, t] );
|
||||
|
||||
const renderItem = ( { style, index, item } ) => (
|
||||
<SlideItem
|
||||
@@ -125,6 +140,44 @@ const OnboardingCarousel = ( { closeModal } ) => {
|
||||
/>
|
||||
);
|
||||
|
||||
const totalImages = ONBOARDING_SLIDES.length;
|
||||
|
||||
// Preload images; show splash screen in meantime
|
||||
useEffect( () => {
|
||||
const imageSources = ONBOARDING_SLIDES.map( slide => slide.background );
|
||||
|
||||
let loadedCount = 0;
|
||||
|
||||
// Preload each image
|
||||
imageSources.forEach( source => {
|
||||
Image.prefetch( Image.resolveAssetSource( source ).uri )
|
||||
.then( ( ) => {
|
||||
loadedCount += 1;
|
||||
if ( loadedCount === totalImages ) {
|
||||
setTimeout( ( ) => {
|
||||
setImagesLoaded( true );
|
||||
}, 500 );
|
||||
}
|
||||
} )
|
||||
.catch( error => console.error( "Error loading image:", error ) );
|
||||
} );
|
||||
}, [ONBOARDING_SLIDES, totalImages] );
|
||||
|
||||
if ( !imagesLoaded ) {
|
||||
return (
|
||||
<ImageBackground
|
||||
source={require( "images/background/daniel-olah-YNUFtf4qyh0-unsplash.jpg" )}
|
||||
className="flex-1 items-center justify-center"
|
||||
>
|
||||
<INatIcon
|
||||
name="inaturalist"
|
||||
size={130}
|
||||
color={colors.white}
|
||||
/>
|
||||
</ImageBackground>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewWrapper wrapperClassName="bg-black">
|
||||
<StatusBar barStyle="light-content" backgroundColor="black" />
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import OnboardingCarousel from "components/Onboarding/OnboardingCarousel";
|
||||
import Modal from "components/SharedComponents/Modal.tsx";
|
||||
import React from "react";
|
||||
|
||||
const OnboardingCarouselModal = ( {
|
||||
showModal,
|
||||
closeModal
|
||||
} ) => (
|
||||
<Modal
|
||||
showModal={showModal}
|
||||
fullScreen
|
||||
closeModal={closeModal}
|
||||
disableSwipeDirection
|
||||
noAnimation
|
||||
modal={(
|
||||
<OnboardingCarousel
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
export default OnboardingCarouselModal;
|
||||
23
src/navigation/StackNavigators/OnboardingStackNavigator.js
Normal file
23
src/navigation/StackNavigators/OnboardingStackNavigator.js
Normal file
@@ -0,0 +1,23 @@
|
||||
// @flow
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||
import OnboardingCarousel from "components/Onboarding/OnboardingCarousel";
|
||||
import type { Node } from "react";
|
||||
import React from "react";
|
||||
|
||||
const Stack = createNativeStackNavigator( );
|
||||
|
||||
const OnboardingStackNavigator = ( ): Node => (
|
||||
<Stack.Navigator>
|
||||
<Stack.Screen
|
||||
name="Onboarding"
|
||||
component={OnboardingCarousel}
|
||||
options={{
|
||||
headerShown: false,
|
||||
presentation: "modal",
|
||||
contentStyle: { backgroundColor: "transparent" }
|
||||
}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
);
|
||||
|
||||
export default OnboardingStackNavigator;
|
||||
@@ -6,8 +6,10 @@ import {
|
||||
} from "navigation/navigationOptions";
|
||||
import LoginStackNavigator from "navigation/StackNavigators/LoginStackNavigator";
|
||||
import NoBottomTabStackNavigator from "navigation/StackNavigators/NoBottomTabStackNavigator";
|
||||
import OnboardingStackNavigator from "navigation/StackNavigators/OnboardingStackNavigator";
|
||||
import type { Node } from "react";
|
||||
import * as React from "react";
|
||||
import { useOnboardingShown } from "sharedHelpers/installData.ts";
|
||||
|
||||
import BottomTabNavigator from "./BottomTabNavigator";
|
||||
import CustomDrawerContent from "./CustomDrawerContent";
|
||||
@@ -33,27 +35,41 @@ const drawerRenderer = ( { state, navigation, descriptors } ) => (
|
||||
);
|
||||
|
||||
// DEVELOPERS: do you need to add any screens here? All the rest of our screens live in
|
||||
// NoBottomTabStackNavigator, TabStackNavigator, or LoginStackNavigator
|
||||
// NoBottomTabStackNavigator, TabStackNavigator, OnboardingStackNavigator, or LoginStackNavigator
|
||||
|
||||
const RootDrawerNavigator = ( ): Node => (
|
||||
<Drawer.Navigator
|
||||
screenOptions={drawerOptions}
|
||||
name="Drawer"
|
||||
drawerContent={drawerRenderer}
|
||||
>
|
||||
<Drawer.Screen
|
||||
name="TabNavigator"
|
||||
component={BottomTabNavigator}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="NoBottomTabStackNavigator"
|
||||
component={NoBottomTabStackNavigator}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="LoginStackNavigator"
|
||||
component={LoginStackNavigator}
|
||||
/>
|
||||
</Drawer.Navigator>
|
||||
);
|
||||
const RootDrawerNavigator = ( ): Node => {
|
||||
const [onboardingShown] = useOnboardingShown( );
|
||||
|
||||
return (
|
||||
<Drawer.Navigator
|
||||
screenOptions={drawerOptions}
|
||||
name="Drawer"
|
||||
drawerContent={drawerRenderer}
|
||||
>
|
||||
{!onboardingShown
|
||||
? (
|
||||
<Drawer.Screen
|
||||
name="OnboardingStackNavigator"
|
||||
component={OnboardingStackNavigator}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Drawer.Screen
|
||||
name="TabNavigator"
|
||||
component={BottomTabNavigator}
|
||||
/>
|
||||
|
||||
)}
|
||||
<Drawer.Screen
|
||||
name="NoBottomTabStackNavigator"
|
||||
component={NoBottomTabStackNavigator}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="LoginStackNavigator"
|
||||
component={LoginStackNavigator}
|
||||
/>
|
||||
</Drawer.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootDrawerNavigator;
|
||||
|
||||
Reference in New Issue
Block a user