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

Component unmounts when going to next screen #224

Open adaryabegi opened 2 years ago

adaryabegi commented 2 years ago

Hey guys. I am struggling here on an issue I can't figure out. So basically what I have, is 1 image that I want to transition into a full screen Image on a new screen. I followed the docs and its no big hack, just really simple, but as soon as the Image is almost on the new screen, it unmounts or something and then shows again.

Showcase of the problem:

https://user-images.githubusercontent.com/56917803/149679950-dd454864-e3a9-454c-a796-06b191ef3fc8.mov

I have a Navigator:

import { createSharedElementStackNavigator } from "react-navigation-shared-element"
import ChatImgGallery from "./ChatImgGallery"
import OneToOneScreen from "./OneToOneScreen"

const iosTransitionSpec = {
  animation: "spring",
  config: {
    stiffness: 1000,
    damping: 500,
    mass: 3,
    overshootClamping: true,
    restDisplacementThreshold: 10,
    restSpeedThreshold: 10,
  },
}

const options = {
  cardStyleInterpolator: ({ current: { progress } }) => {
    return { cardStyle: { opacity: progress } }
  },
  gestureEnabled: false,
  // ...TransitionPresets.ModalSlideFromBottomIOS,
  transitionSpec: {
    open: iosTransitionSpec,
    close: iosTransitionSpec,
  },
}

const ChatStack = createSharedElementStackNavigator()

export default function ChatNavigator() {
  return (
    <ChatStack.Navigator
      screenOptions={{ headerShown: false, useNativeDriver: true }}
    >
      <ChatStack.Screen name="OneToOneChat" component={OneToOneScreen} />
      <ChatStack.Screen
        name="ChatImgGallery"
        component={ChatImgGallery}
        sharedElements={(route, otherRoute, showing) => {
          const { item, uri } = route.params
          return [`item.${uri}.photo`]
        }}
        options={() => options}
      />
    </ChatStack.Navigator>
  )
}

This is a Component called "NormalMessage" inside the "OneToOneScreen":

{message?.files?.length > 0
          ? message?.files.map((file, index) => (
              <TouchableOpacity
                style={{
                  width: MAX_IMG_WIDTH,
                  height: MAX_IMG_WIDTH,
                  borderRadius: 19,
                  marginTop:
                    index <= message.files.length - 1 && index !== 0 ? 10 : 0,
                }}
                onPress={() =>
                  navigate("ChatImgGallery", {
                    item: message,
                    file: index,
                    uri: file.location,
                  })
                }
              >
                <SharedElement id={`item.${file.location}.photo`}>
                  <Image
                    source={{ uri: file.location }}
                    style={{
                      width: "100%",
                      height: "100%",
                      borderRadius: 19,
                    }}
                  />
                </SharedElement>
              </TouchableOpacity>
            ))
          : null}

This is the component "ChatImgGallery":

import React from "react"
import {
  View,
  StyleSheet,
  Image,
  TouchableOpacity,
  Dimensions,
} from "react-native"
import { SharedElement } from "react-navigation-shared-element"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { useNavigation } from "@react-navigation/native"
import { Ionicons } from "@expo/vector-icons"

const { height, width } = Dimensions.get("window")

export default function ChatImgGallery({ route }) {
  const { bottom, top } = useSafeAreaInsets()
  const { item, uri } = route.params
  const { goBack } = useNavigation()

  return (
    <View style={styles.container}>
      <View style={[styles.topView, { paddingTop: top + 10 }]}>
        <TouchableOpacity onPress={goBack}>
          <Ionicons name="close-outline" size={24} color="white" />
        </TouchableOpacity>
      </View>
      <SharedElement id={`item.${uri}.photo`}>
        <Image
          source={{ uri }}
          style={{ width, height }}
          resizeMode="cover"
          load
        />
      </SharedElement>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFillObject,
  },
  imgView: {
    ...StyleSheet.absoluteFillObject,
  },
  topView: {
    width: "100%",
    backgroundColor: "rgba(0,0,0,0.6)",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    paddingHorizontal: 20,
    position: "absolute",
    left: 0,
    top: 0,
    zIndex: 2,
  },
})

Important package versions:

    "expo": "~44.0.0",
    "react": "17.0.1",
    "react-dom": "17.0.1",
    "react-native": "0.64.3",
    "@react-navigation/native": "5.x.x",
    "@react-navigation/stack": "5.x.x",
    "react-native-screens": "~3.0.0",
    "react-native-shared-element": "^0.8.3",
    "react-navigation": "^4.4.4",
    "react-navigation-shared-element": "^3.1.3",
    "react-navigation-stack": "^2.10.4",
dirmich commented 2 years ago

add cardStyle: {backgroundColor: 'transparent'} to options

Engazan commented 2 years ago

for me this helper add this styles to your Screen opntions

                   cardStyle: {
                        backgroundColor: 'transparent'
                    },
                    cardStyleInterpolator: ({current}) => {
                        return {
                            cardStyle: {
                                opacity: current.progress,
                                backgroundColor: 'transparent'
                            }
                        }
                    }

example from my code

              <Stack.Screen
                name="TicketDetailScreen"
                component={TicketDetailScreen}
                options={{
                    gestureEnabled: false,
                    transitionSpec: {
                        open: {animation: 'timing', config: {duration: 300}},
                        close: {animation: 'timing', config: {duration: 300}}
                    },
                    cardStyle: {
                        backgroundColor: 'transparent'
                    },
                    cardStyleInterpolator: ({current}) => {
                        return {
                            cardStyle: {
                                opacity: current.progress,
                                backgroundColor: 'transparent'
                            }
                        }
                    }
                }}
            />
pouyarezvani commented 1 year ago

@dirmich cardStyle: {backgroundColor: 'transparent'} didnt fix it for me. Any other ideas? What is even the point of transparency? this makes the page see-through. In that case, I would have to assign a background color to the scroll view, and it's not a good UI behavior. My image still flickers just like the original comment on this post.

@adaryabegi Did you find a solution?

pouyarezvani commented 1 year ago

I was able to reproduce it in the video shared below. The second image is the one that flickers after it navigates. Both Images are loaded from the Cloud with URI Could it have to do with the quality of the image? not sure. @IjzerenHein any ideas?

https://user-images.githubusercontent.com/32917154/226147041-e4f4ebb6-95e5-4ced-9ab9-b41a64b76421.MP4

IjzerenHein commented 1 year ago

@pouyarezvani I think this has to do with caching and the second image being larger in size. Could you log out the sizes of both images? Also, is it always the second image in the list that produces the problem, or does it happen to particular image-url's and not to others?

VictorioMolina commented 1 year ago

@IjzerenHein Same here. In my case, images loaded from cache too, with same dimensions (aprox) and a size of 100KB.

I am using expo-image (most recent version).