IjzerenHein / react-navigation-shared-element

React Navigation bindings for react-native-shared-element 💫
https://github.com/IjzerenHein/react-native-shared-element
MIT License
1.27k stars 124 forks source link

Closing animation is glitching only on iOS when updating to react navigation v6 #186

Open haibert opened 3 years ago

haibert commented 3 years ago

As shown in the gif bellow the animations does not work correctly when going back. On android it works correctly. I have spent all day trouble shooting so i'm pretty sure i covered everything. I have turned on debug mode and made sure all ids are passed correctly. I de-nested the shared element navigator and made it the root navigator for testing purposes it still did not make a difference. Also it is strange that it's an iOS issue only, usually its android giving the issues with this library.

Ive tried different modal presentations, turning off modal completely, trying with and without transition specs. Nothing seems to fix the issue. I want to mention that the same exact code (minus the renaming of certain props) works fine with react navigation v5.

If I need to provide a repository please let me know and I will direct message you on twitter or where you'd like hopefully. Since the repository is not public and the APIs need to be live for content to show.

ezgif com-gif-maker (9)

const iosTransitionSpec = {
    animation: 'spring',
    config: {
        stiffness: 1000,
        damping: 1000,
        mass: Platform.OS === 'android' ? 3 : 1.9,
        overshootClamping: true,
        restDisplacementThreshold: 10,
        restSpeedThreshold: 10,
    },
}

const cardStyleInterpolatorFunc = ({ current }) => {
    return {
        cardStyle: {
            opacity: current.progress,
        },
    }
}

const gestureResponseVertical = 1000

const gestureResponseHorizontal = Platform.OS === 'android' ? 50 : 1000

const gestureDirectionVertical = 'vertical'
const gestureDirectionHorizontal = 'horizontal'

const FeedSEStack = createSharedElementStackNavigator({
    name: 'FeedSEStack',
    debug: true,
})
const FeedSharedElementStack = ({ navigation, route }) => {
    const platformAnimation = useMemo(
        () => (Platform.OS === 'android' ? 'fade-out' : 'move'),
        []
    )

    return (
        <FeedSEStack.Navigator
            initialRouteName="DashboardScreen"
            screenOptions={{
                headerShown: false,
            }}
            detachInactiveScreens={true}
        >
            <FeedSEStack.Screen
                name="DashboardScreen"
                component={FeedScreen}
                options={{
                    animationEnabled: false,
                }}
            />
            <FeedSEStack.Screen
                name="ProfileScreen"
                component={ProfileScreen}
                options={() => ({
                    animationEnabled: false,
                })}
            />
            <FeedSEStack.Screen
                name="GalleryView"
                component={GalleryView}
                options={() => ({
                    gestureEnabled: false,
                    cardStyle: {
                        backgroundColor: 'transparent',
                    },
                    cardStyleInterpolator: cardStyleInterpolatorFunc,
                    presentation: 'transparentModal',
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                })}
                sharedElements={(route) => {
                    const { galleryID, galName } = route.params

                    return [
                        {
                            id: galleryID,
                            animation:
                                Platform.OS === 'android' ? 'fade-out' : 'move',
                            resize: 'auto',
                            align: 'auto',
                        },
                    ]
                }}
            />
            <FeedSEStack.Screen
                name="OtherGalleryView"
                component={OtherGalleryView}
                options={() => ({
                    gestureEnabled: false,
                    useNativeDriver: true,
                    cardStyle: {
                        backgroundColor: 'transparent',
                    },
                    cardStyleInterpolator: cardStyleInterpolatorFunc,
                    presentation: 'transparentModal',
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                })}
                sharedElements={(route) => {
                    const { galleryID, galName } = route.params
                    return [
                        {
                            id: [galleryID],
                            animation: platformAnimation,
                            resize: 'auto',
                            align: 'auto',
                        },
                        {
                            id: [galleryID + galName],
                            animation: 'fade',
                            resize: 'clip',
                            align: 'auto',
                        },
                    ]
                }}
            />
            <FeedSEStack.Screen
                name="GalleryDetailScreen"
                component={GalleryDetailScreen}
                options={() => ({
                    gestureResponseDistance: gestureResponseVertical,
                    gestureDirection: gestureDirectionVertical,
                    presentation: 'transparentModal',
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                })}
                sharedElements={(route) => {
                    const { picID } = route.params
                    return [
                        {
                            id: Platform.OS === 'android' ? picID : picID,
                            animation:
                                Platform.OS === 'android' ? 'fade-out' : 'move',
                            resize: 'auto',
                        },
                    ]
                }}
            />

            <FeedSEStack.Screen
                name="ProfileEditScreen"
                component={ProfileEditScreen}
                options={{
                    gestureResponseDistance: gestureResponseVertical,
                    gestureDirection: gestureDirectionVertical,
                    ...TransitionPresets.ModalSlideFromBottomIOS,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
                sharedElements={(navigation) => {
                    const animatePlease = navigation.name === 'ProfileScreen'
                    return [
                        {
                            id: animatePlease ? '1' : null,
                            animation:
                                Platform.OS === 'android' ? 'fade-out' : null,
                            resize: 'auto',
                        },
                    ]
                }}
            />

            <FeedSEStack.Screen
                name="SearchScreen"
                component={SearchScreen}
                options={{
                    animationEnabled: false,
                }}
            />
            <FeedSEStack.Screen
                name="OtherProfileScreen"
                component={OtherProfileScreen}
                options={{
                    gestureResponseDistance: gestureResponseHorizontal,
                    gestureDirection: gestureDirectionHorizontal,
                    ...TransitionPresets.SlideFromRightIOS,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
            />

            {/* ALL HORIZONTAL SCREENS BELLOW */}

            <FeedSEStack.Screen
                name="ProfileFollowsScreen"
                component={ProfileFollowsScreen}
                options={{
                    gestureResponseDistance: gestureResponseHorizontal,
                    gestureDirection: gestureDirectionHorizontal,
                    ...TransitionPresets.SlideFromRightIOS,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
            />
            <FeedSEStack.Screen
                name="OtherFollowsScreen"
                component={OtherFollowsScreen}
                options={{
                    ...TransitionPresets.SlideFromRightIOS,
                    gestureResponseDistance: gestureResponseHorizontal,
                    gestureDirection: gestureDirectionHorizontal,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
            />

            {/* <FeedSEStack.Screen
                name="NotificationsScreen"
                component={NotificationsScreen}
                options={{
                    headerShown: false,
                    ...TransitionPresets.SlideFromRightIOS,
                    gestureResponseDistance: gestureResponseHorizontal,
                    gestureDirection: gestureDirectionHorizontal,
                }}
            /> */}

            {/* ALL EDIT SCREENS BELLOW */}
            <FeedSEStack.Screen
                name="EditNameScreen"
                component={EditNameScreen}
                options={{
                    gestureResponseDistance: gestureResponseVertical,
                    gestureDirection: gestureDirectionVertical,
                    ...TransitionPresets.ModalSlideFromBottomIOS,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
            />
            <FeedSEStack.Screen
                name="EditBirthdayScreen"
                component={EditBirthdayScreen}
                options={{
                    gestureResponseDistance: gestureResponseVertical,
                    gestureDirection: gestureDirectionVertical,
                    ...TransitionPresets.ModalSlideFromBottomIOS,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
            />

            <FeedSEStack.Screen
                name="EditPhoneScreen"
                component={EditPhoneScreen}
                options={{
                    gestureResponseDistance: gestureResponseVertical,
                    gestureDirection: gestureDirectionVertical,
                    ...TransitionPresets.ModalSlideFromBottomIOS,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
            />
            <FeedSEStack.Screen
                name="EditUsernameScreen"
                component={EditUsernameScreen}
                options={{
                    gestureResponseDistance: gestureResponseVertical,
                    gestureDirection: gestureDirectionVertical,
                    ...TransitionPresets.ModalSlideFromBottomIOS,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
            />
            <FeedSEStack.Screen
                name="EditGalleryScreen"
                component={EditGalleryScreen}
                options={{
                    gestureResponseDistance: gestureResponseVertical,
                    gestureDirection: gestureDirectionVertical,
                    ...TransitionPresets.ModalSlideFromBottomIOS,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
            />
            <FeedSEStack.Screen
                name="NotificationsScreen"
                component={NotificationsScreen}
                options={{
                    ...TransitionPresets.SlideFromRightIOS,
                    gestureResponseDistance: gestureResponseHorizontal,
                    gestureDirection: gestureDirectionHorizontal,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
            />
            <FeedSEStack.Screen
                name="CommentsScreen"
                component={CommentsScreen}
                options={{
                    ...TransitionPresets.SlideFromRightIOS,
                    gestureResponseDistance: gestureResponseHorizontal,
                    gestureDirection: gestureDirectionHorizontal,
                    transitionSpec: {
                        open: iosTransitionSpec,
                        close: iosTransitionSpec,
                    },
                }}
            />
        </FeedSEStack.Navigator>
    )
}
IjzerenHein commented 3 years ago

Hi! Please add a testcase to the example app that reproduces the problem? This way I can fix the problem and verify that it will keep working in the future.

haibert commented 3 years ago

Hi Hein, Sorry for the delay I have finally uploaded the files to the example app. All 3 files start with "NavV6" They are reduced down to the essentials. If it does not work I have also turned it into a repo here. If you would entertain the thought of cloning it and trying it out please.

https://github.com/haibert/Shared-Element-NavV6-Issue

haibert commented 3 years ago

I also want to mention, as amazing as this library is, you start to see significant performance loss when using it instead of createStackNavigator. At least in my situation... I have 4 different createSharedElementStackNavigator stacks that are nested inside a bottom tab navigator as the root navigator for the app. Hopefully fabric will resolve performance issues like this...

IjzerenHein commented 3 years ago

This was just merged: https://github.com/software-mansion/react-native-screens/pull/890 So we should be one step closer to making the native-stack a reality

Didn't you also send me an email regarding sponsorship for react-native-shared-element? Was that sincere and are you still considering sponsoring?

haibert commented 3 years ago

Absolutely, we appreciate open source contributors/maintainers at our company even though we are small. Should we continue this conversation vis email or Twitter DMs to discuss details? My handle is @haibert8

IjzerenHein commented 3 years ago

Cool! I'm out of town for a couple days but will contact you somewhere next week @haibert

haibert commented 3 years ago

Awesome, sounds good!

hirbod commented 3 years ago

I'll drop a donation when native-stack is supported!

nandorojo commented 3 years ago

I am also happy to sponsor for shared elements in the native-stack!

mrousavy commented 3 years ago

@IjzerenHein hey just tuning in to say that I'm also happy to help working on the native stack implementation! (the challenge seems interesting lol)

nandorojo commented 3 years ago

Well this is getting quite exciting.

wibb36 commented 2 years ago

Same here. Love it. I'd be happy to sponsor too.

IjzerenHein commented 2 years ago

Hello everyone! Thanks for all the willingness to sponsor and the encouragement regarding the native-stack implementation. I've created an official money-pool for collecting donations to make this is reality. Love, Hein https://paypal.me/pools/c/8Cyt3ED5wV

haibert commented 2 years ago

Simplyrem has contributed 🙏🏻 let's share the PayPal link guys! Hein I am curious if you can use JSI for this library? It seems like a surefire of making sure the performance is amazing. @mrousavy is an expert at JSI and he is willing to help out with this mission.

hirbod commented 2 years ago

So did I. I closed my MoneyPool, nobody dropped a cent there :D...

nandorojo commented 2 years ago

$100 sent. JSI would be great.

nandorojo commented 2 years ago

I tweeted about this here too: https://twitter.com/FernandoTheRojo/status/1433137962074652672?s=20 Feel free to retweet it so more people can see.

wibb36 commented 2 years ago

$100 sent.

IjzerenHein commented 2 years ago

@haibert I've had a look at the repo you provided, and was able to get it work correctly. Honestly, the https://github.com/haibert/Shared-Element-NavV6-Issue repo was a bit messy. The id's used in the FeedScreen were not the same as in the OtherGalleryView screen (which caused no correct transitions to happen at all). Also, the sharedElements function return an array as an id, instead of a string.

sharedElements={(route) => {
          const { galleryID } = route.params;
          return [
            {
              id: [galleryID], // THIS is wrong, should be just `galleyID`
              animation: platformAnimation,
              resize: "auto",
              align: "auto",
            },
          ];
        }}

Also, OtherGalleryView contained this weird full-screen SharedElement which used the galleryId. Is this intentional?

<View style={{ flex: 1, backgroundColor: "white" }}>
      {/* <SharedElement id={`${galleryID}`} style={styles.sharedElement}>
        <Image
          style={styles.imageBg}
          source={{
            uri: thumbnail,
          }}
          resizeMode="cover"
        />
        </SharedElement> */}
      <FlatList
        data={pics}

I disabled that and changed the shared element id in ThumbnailSmall to that of the galleryID.

const ThumbnailSmall = ({ images }) => {
    return (
      <Pressable onPress={picturePressedHandler} style={styles.cont}>
---        <SharedElement id={images.id}>
+++        <SharedElement id={images.galleryID}>
          <Image
            style={styles.image}
            source={{
              uri: images.thumbPath,
            }}
            resizeMode="cover"
          />
        </SharedElement>
      </Pressable>
    );
  };

After that, things started working. However, when navigating back, the position was incorrect. This was because the app was a managed Expo app using Expo SDK 42. This still used the native code react-native-shared-element@0.7.0. After ejecting and using the latest native code react-native-shared-element@0.8.x, the transitions worked as expected, as you can see in this preview. Your car is now transitioning perfectly.

https://user-images.githubusercontent.com/6184593/132223826-dd0afb2b-be96-4e1f-af58-b202ecae18f3.mp4

haibert commented 2 years ago

Hello Hein,

Thank you for providing a fix. The reason I had different ID's in the feed screen vs the gallery view screen is because after you open the gallery from the feed you are navigated inside the gallery, and when you tap any pictures inside the gallery it takes you to the full screen version of that photo with a shared transition again. So basically it's a shared transition from screen A to screen B and then another shared transition form screen B to screen C. This should be possible correct? You have essentially reversed the order of navigation in the fix but that is okay, I have scratched this implementation for now until the native stack is hopefully supported.

Do you know if expo SDK 43 will support react-native-shared-element@0.8.x ?

IjzerenHein commented 2 years ago

Thank you for providing a fix. The reason I had different ID's in the feed screen vs the gallery view screen is because after you open the gallery from the feed you are navigated inside the gallery, and when you tap any pictures inside the gallery it takes you to the full screen version of that photo with a shared transition again. So basically it's a shared transition from screen A to screen B and then another shared transition form screen B to screen C. This should be possible correct? You have essentially reversed the order of navigation in the fix but that is okay, I have scratched this implementation for now until the native stack is hopefully supported.

Okay I think I understand, so there's the feed (which is a gallery) and this navigates to another gallery, and when you press on an item there, it shows the full-photo. However this is now what's in in the repo you provided, it only has two screens (both a gallery), so I was assuming you wanted to animate between those. I'm sorry, your example confuses me too much, I honestly don't get what you are trying to achieve..

Do you know if expo SDK 43 will support react-native-shared-element@0.8.x ?

Yep, the PR has already landed in the codebase: https://github.com/expo/expo/pull/14245

haibert commented 2 years ago

@IjzerenHein Hello Hein, Hope you're doing great! I just wanted to check in and see how far the plans to start work on the createNativeStackNavigator implementation are. I know you've been super busy and the bundle visualizer looks super cool!

andreibarabas commented 2 years ago

Hey! I'm trying to donate the $100 to support the createNativeStackNavigator efforts, but they PayPal money pool link does not work anymore.

Is there anywhere else we can support the development of this feature? @IjzerenHein

bfischer1121 commented 2 years ago

Would love to see this implemented @IjzerenHein Can donate if needed