software-mansion / react-native-reanimated

React Native's Animated library reimplemented
https://docs.swmansion.com/react-native-reanimated/
MIT License
9.04k stars 1.31k forks source link

Shared Element Transition with Text does not work #5212

Open marcuzgabriel opened 1 year ago

marcuzgabriel commented 1 year ago

Description

Shared element transition with Animated.Text or Animated.View (as wrapper around Text) component does not work. The wrapper itself does work (please see border). I am trying to transition an emoji. I believe it might be because there is no support for Text transition yet based on: https://github.com/software-mansion/react-native-reanimated/issues/4990.

Whether it is an issue or a feature request I will let you decide as I lack context. Would be much appreciated if you could point me in the direction of a solution or alternatively let me know whether there will be support for it in the future.

Right now my only option is to go with the react-navigation-shared-element package which is no longer maintained πŸ˜Άβ€πŸŒ«οΈ so my colleagues are not fund of me 😩

https://github.com/software-mansion/react-native-reanimated/assets/42548700/4bb55670-7606-4bbc-9fba-5c2d004018be

Steps to reproduce

import React, { useMemo } from 'react';
import { StyleSheet, Text as RNText, ColorValue, View } from 'react-native';
import Animated, { SharedTransition, withSpring } from 'react-native-reanimated';
import { useTheme } from '@somecompany/mobile-insurance-app/hooks';
import { Text } from '@somecompany/mobile-insurance-ui/components/atoms';
import { FontType } from '@somecompany/mobile-insurance-ui/components/atoms/Text';
import { DEFAULT_SPRING_CONFIG } from '@somecompany/mobile-insurance-ui/constants';

const BORDER_WIDTH = 2;
export interface AvatarProps {
  size: 'xxLarge' | 'xLarge' | 'large' | 'small';
  style?: 'primary' | 'secondary' | 'tertiary' | 'stacked';
  applyBorder?: boolean;
  hasBorderRadius?: boolean;
  sharedElementID?: string | undefined;
  text?: string;
  emoji?: string;
  testID?: string;
}

const styles = StyleSheet.create({
  circle: {
    alignItems: 'center',
    aspectRatio: 1,
    justifyContent: 'center',
  },
});

interface AvatarStyle {
  size?: number;
  backgroundColor: ColorValue | undefined;
  borderColor: ColorValue | undefined;
  borderWidth: number | undefined;
  hasBorderRadius?: boolean | undefined;
  font?: FontType;
  emojiSize?: number;
}

const Avatar: React.FC<AvatarProps> = ({
  size = 'large',
  style = 'primary',
  applyBorder = false,
  hasBorderRadius = true,
  text,
  emoji,
  sharedElementID,
}) => {
  const { COLORS } = useTheme();
  const customTransition = SharedTransition.custom(values => {
    'worklet';

    return {
      width: withSpring(values.targetWidth, DEFAULT_SPRING_CONFIG),
      height: withSpring(values.targetHeight, DEFAULT_SPRING_CONFIG),
      originX: withSpring(values.targetOriginX, DEFAULT_SPRING_CONFIG),
      originY: withSpring(values.targetOriginY, DEFAULT_SPRING_CONFIG),
    };
  });

  const avatarStyle: AvatarStyle = useMemo(() => {
    let borderColor: ColorValue | undefined;
    let backgroundColor: ColorValue | undefined;

    if (applyBorder && style !== 'stacked') {
      borderColor = COLORS.SURFACE.COLOR_SURFACE_PRIMARY;
    } else if (style === 'stacked') {
      borderColor = COLORS.SURFACE.COLOR_SURFACE_SECONDARY;
    }

    const borderWidth = borderColor ? BORDER_WIDTH : undefined;

    switch (style) {
      case 'primary':
        backgroundColor = COLORS.SURFACE.COLOR_SURFACE_PRIMARY;
        break;
      case 'secondary':
        backgroundColor = COLORS.SURFACE.COLOR_SURFACE_SECONDARY;
        break;
      case 'tertiary':
        backgroundColor = COLORS.SURFACE.COLOR_SURFACE_TERTIARY;
        break;
      case 'stacked':
        backgroundColor = undefined;
        break;
    }

    switch (size) {
      case 'xxLarge':
        return {
          size: 108,
          backgroundColor,
          borderColor,
          borderWidth,
          font: 'titleHero',
          emojiSize: 108,
        };
      case 'xLarge':
        return {
          size: 72,
          backgroundColor,
          borderColor,
          borderWidth,
          font: 'titleLarge',
          emojiSize: 24,
        };

      case 'large':
        return {
          size: 48,
          backgroundColor,
          borderColor,
          borderWidth,
          font: text !== undefined ? 'h1' : 'h2',
          emojiSize: 24,
        };

      case 'small':
        return {
          size: 32,
          backgroundColor,
          borderColor,
          borderWidth,
          font: text !== undefined ? 'h2' : 'body',
          emojiSize: 16,
        };
    }
  }, [COLORS, applyBorder, style, size, text]);

  return (
    <View
      style={[
        styles.circle,
        {
          backgroundColor: avatarStyle.backgroundColor,
          borderColor: avatarStyle.borderColor,
          ...(hasBorderRadius && avatarStyle?.size
            ? {
                width: avatarStyle.size - (avatarStyle.borderWidth ?? 0),
                borderRadius: avatarStyle.size / 2,
              }
            : {}),
        },
      ]}
    >
      {emoji && avatarStyle.emojiSize && (
        <Animated.View
          {...(sharedElementID ? { sharedTransitionTag: `item.${sharedElementID}.container` } : {})}
          sharedTransitionStyle={customTransition}
          style={{ borderWidth: 2 }}
        >
          <RNText style={{ fontSize: avatarStyle.emojiSize }}>{emoji}</RNText>
        </Animated.View>
        /* NOTE: I have also tried with an Animated.Text component instead of RNText, but it didn't work either 
        <Animated.Text
          {...(sharedElementID ? { sharedTransitionTag: `item.${sharedElementID}.container` } : {})}
          sharedTransitionStyle={customTransition}
          style={{ fontSize: avatarStyle.emojiSize }}
        >
          {emoji}
        </Animated.Text>
        */
      )}
    </View>
  );
};

export default Avatar;

Snack or a link to a repository

www.haventmaderepo.com/youneed?

Reanimated version

3.5.2

React Native version

0.72+

Platforms

Android, iOS

JavaScript runtime

Hermes

Workflow

React Native

Architecture

Fabric (New Architecture)

Build type

Debug mode

Device

iOS simulator

Device model

iPhone 15

Acknowledgements

Yes

github-actions[bot] commented 1 year ago

Hey! πŸ‘‹

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

kicaal commented 1 year ago

I have the same problem with the text or view that is inside the view that has the shared tag. Any ideas to fix this?

https://github.com/software-mansion/react-native-reanimated/assets/40881553/f427e2b5-5a0a-4a04-a0d3-811102a7f965