Skip to content

Commit

Permalink
Fix address copy crash (and floating emojis animation) (#6392)
Browse files Browse the repository at this point in the history
  • Loading branch information
brunobar79 authored Jan 10, 2025
1 parent 73c782d commit 91af6c1
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,6 @@ export function CopyButton() {

return (
<>
{/* @ts-expect-error JavaScript component */}
<CopyFloatingEmojis textToCopy={accountAddress}>
<ActionButton onPress={handlePressCopy} icon="􀐅" testID="receive-button">
{lang.t('wallet.copy')}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import React from 'react';
import React, { FC, ReactNode } from 'react';
import { ButtonPressAnimation } from '../animations';
import FloatingEmojis from './FloatingEmojis';
import { useClipboard } from '@/hooks';
import { magicMemo } from '@/utils';

const CopyFloatingEmojis = ({ children, disabled, onPress, textToCopy, ...props }) => {
interface CopyFloatingEmojisProps {
/** Child elements or nodes to render inside this component */
children?: ReactNode;
/** Whether the floating emojis and copy functionality is disabled */
disabled?: boolean;
/**
* Callback to run on press.
* Receives `textToCopy` (if provided) as an argument.
*/
onPress?: (textToCopy?: string) => void;
/** The text that should be copied to the clipboard */
textToCopy?: string;
/** Any additional props you want to forward to FloatingEmojis */
[key: string]: unknown;
}

const CopyFloatingEmojis: FC<CopyFloatingEmojisProps> = ({ children, disabled = false, onPress, textToCopy, ...props }) => {
const { setClipboard } = useClipboard();

return (
<FloatingEmojis distance={250} duration={500} fadeOut={false} scaleTo={0} size={50} wiggleFactor={0} {...props}>
<FloatingEmojis emojis={['thumbs_up']} distance={250} duration={500} fadeOut={false} scaleTo={0} size={50} wiggleFactor={0} {...props}>
{({ onNewEmoji }) => (
<ButtonPressAnimation
hapticType="impactLight"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import PropTypes from 'prop-types';
import React, { useLayoutEffect } from 'react';
import Animated, { Easing, interpolate, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
import { Emoji } from '../text';

const FloatingEmoji = ({
interface FloatingEmojiProps {
centerVertically?: boolean;
disableHorizontalMovement?: boolean;
disableVerticalMovement?: boolean;
distance: number;
duration: number;
emoji: string;
fadeOut?: boolean;
index: number;
left: number;
marginTop?: number;
opacityThreshold?: number;
scaleTo: number;
size: string;
top?: number;
wiggleFactor?: number;
}

const FloatingEmoji: React.FC<FloatingEmojiProps> = ({
centerVertically,
disableHorizontalMovement,
disableVerticalMovement,
Expand Down Expand Up @@ -35,7 +52,7 @@ const FloatingEmoji = ({

const opacity = interpolate(
progress,
[0, distance * (opacityThreshold ?? 0.5), distance - size],
[0, distance * (opacityThreshold ?? 0.5), distance - Number(size)],
[1, fadeOut ? 0.89 : 1, fadeOut ? 0 : 1]
);

Expand All @@ -45,29 +62,29 @@ const FloatingEmoji = ({

const everyThirdEmojiMultiplier = index % 3 === 0 ? 3 : 2;
const everySecondEmojiMultiplier = index % 2 === 0 ? -1 : 1;
const translateXComponentA = animation.value * size * everySecondEmojiMultiplier * everyThirdEmojiMultiplier;

/*
We don't really know why these concrete numbers are used there.
Original Author of these numbers: Mike Demarais
*/
// Horizontal movement
const translateXComponentA = animation.value * Number(size) * everySecondEmojiMultiplier * everyThirdEmojiMultiplier;

// "Wiggle" calculations
const wiggleMultiplierA = Math.sin(progress * (distance / 23.3));
const wiggleMultiplierB = interpolate(
progress,
[0, distance / 10, distance],
[10 * wiggleFactor, 6.9 * wiggleFactor, 4.2137 * wiggleFactor]
[10 * (wiggleFactor ?? 1), 6.9 * (wiggleFactor ?? 1), 4.2137 * (wiggleFactor ?? 1)]
);
const translateXComponentB = wiggleMultiplierA * wiggleMultiplierB;

const translateX = disableHorizontalMovement ? 0 : translateXComponentA + translateXComponentB;

// Vertical movement
const translateY = disableVerticalMovement ? 0 : -progress;

return {
opacity,
transform: [{ rotate }, { scale }, { translateX }, { translateY }],
};
}, []);
});

return (
<Animated.View
Expand All @@ -76,31 +93,16 @@ const FloatingEmoji = ({
left,
marginTop,
position: 'absolute',
top: centerVertically ? null : top || size * -0.5,
top: centerVertically ? undefined : top ?? Number(size) * -0.5,
},
animatedStyle,
]}
>
<Emoji name={emoji} size={size} />
<Emoji name={emoji} />
</Animated.View>
);
};
FloatingEmoji.propTypes = {
centerVertically: PropTypes.bool,
disableHorizontalMovement: PropTypes.bool,
disableVerticalMovement: PropTypes.bool,
distance: PropTypes.number.isRequired,
duration: PropTypes.number.isRequired,
emoji: PropTypes.string.isRequired,
fadeOut: PropTypes.bool,
left: PropTypes.string.isRequired,
marginTop: PropTypes.number,
opacityThreshold: PropTypes.number,
scaleTo: PropTypes.number.isRequired,
size: PropTypes.string.isRequired,
top: PropTypes.number,
wiggleFactor: PropTypes.number,
};

const neverRerender = () => true;
const neverRerender = (): boolean => true;

export default React.memo(FloatingEmoji, neverRerender);
87 changes: 41 additions & 46 deletions src/components/floating-emojis/FloatingEmojis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import FloatingEmoji from './FloatingEmoji';
import GravityEmoji from './GravityEmoji';
import { useTimeout } from '@/hooks';
import { position } from '@/styles';
import { DebugLayout } from '@/design-system';
import { DEVICE_HEIGHT, DEVICE_WIDTH } from '@/utils/deviceUtils';
import { AbsolutePortal } from '../AbsolutePortal';

interface Emoji {
emojiToRender: string;
Expand Down Expand Up @@ -101,49 +98,47 @@ const FloatingEmojis: React.FC<FloatingEmojisProps> = ({
return (
<View style={[{ zIndex: 1 }, style]} {...props}>
{typeof children === 'function' ? children({ onNewEmoji }) : children}
<AbsolutePortal>
<Animated.View
pointerEvents="none"
style={{
opacity,
...position.coverAsObject,
}}
>
{gravityEnabled
? floatingEmojis.map(({ emojiToRender, x, y }, index) => (
<GravityEmoji
key={`${x}${y}`}
distance={Math.ceil(distance)}
duration={duration}
emoji={emojiToRender}
index={index}
left={typeof size === 'number' ? x - size / 2 : x - Number(size) / 2}
size={size}
top={y}
/>
))
: floatingEmojis.map(({ emojiToRender, x, y }, index) => (
<FloatingEmoji
centerVertically={centerVertically}
disableHorizontalMovement={disableHorizontalMovement}
disableVerticalMovement={disableVerticalMovement}
distance={Math.ceil(distance)}
duration={duration}
emoji={emojiToRender}
fadeOut={fadeOut}
index={index}
key={`${x}${y}`}
left={`${x}`}
marginTop={marginTop}
opacityThreshold={opacityThreshold}
scaleTo={scaleTo}
size={`${size}`}
top={y}
wiggleFactor={wiggleFactor}
/>
))}
</Animated.View>
</AbsolutePortal>
<Animated.View
pointerEvents="none"
style={{
opacity,
...position.coverAsObject,
}}
>
{gravityEnabled
? floatingEmojis.map(({ emojiToRender, x, y }, index) => (
<GravityEmoji
key={`${x}${y}`}
distance={Math.ceil(distance)}
duration={duration}
emoji={emojiToRender}
index={index}
left={typeof size === 'number' ? x - size / 2 : x - Number(size) / 2}
size={`${size}`}
top={y}
/>
))
: floatingEmojis.map(({ emojiToRender, x, y }, index) => (
<FloatingEmoji
centerVertically={centerVertically}
disableHorizontalMovement={disableHorizontalMovement}
disableVerticalMovement={disableVerticalMovement}
distance={Math.ceil(distance)}
duration={duration}
emoji={emojiToRender}
fadeOut={fadeOut}
index={index}
key={`${x}${y}`}
left={x}
marginTop={marginTop}
opacityThreshold={opacityThreshold}
scaleTo={scaleTo}
size={`${size}`}
top={y}
wiggleFactor={wiggleFactor}
/>
))}
</Animated.View>
</View>
);
};
Expand Down
10 changes: 3 additions & 7 deletions src/components/floating-emojis/GravityEmoji.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface GravityEmojiProps {
emoji: string;
index: number;
left: number;
size: number;
size: string;
top: number;
}

Expand Down Expand Up @@ -74,16 +74,12 @@ const GravityEmoji = ({ distance, emoji, left, size, top }: GravityEmojiProps) =
{
left,
position: 'absolute',
top: top || size * -0.5,
top: top || Number(size) * -0.5,
},
animatedStyle,
]}
>
<Emoji
name={emoji}
// @ts-expect-error – JS component
size={size}
/>
<Emoji name={emoji} size={size} />
</Animated.View>
);
};
Expand Down
28 changes: 0 additions & 28 deletions src/components/text/Emoji.js

This file was deleted.

54 changes: 54 additions & 0 deletions src/components/text/Emoji.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { isString } from 'lodash';
import React, { ReactNode } from 'react';
import Text from './Text';
import { emojis } from '@/references';
import { fonts } from '@/styles';

const emojiData = Object.entries(emojis).map(([emojiChar, { name }]) => {
return [name, emojiChar];
}) as [string, string][];

export const emoji = new Map<string, string>(emojiData);

function normalizeName(name: string): string {
if (/:.+:/.test(name)) {
name = name.slice(1, -1);
}
return name;
}

function getEmoji(name: unknown): string | null {
if (!isString(name)) return null;
const result = emoji.get(normalizeName(name));
return result ?? null;
}

export interface TextProps {
isEmoji?: boolean;
letterSpacing?: string;
lineHeight?: string;
size?: keyof typeof fonts.size;
[key: string]: unknown;
}

export interface EmojiProps extends Omit<TextProps, 'isEmoji' | 'lineHeight' | 'letterSpacing' | 'children'> {
children?: ReactNode;
letterSpacing?: string;
lineHeight?: string;
name?: string;
}

export default function Emoji({
children = undefined,
letterSpacing = 'zero',
lineHeight = 'none',
name,
size = 'h4',
...props
}: EmojiProps) {
return (
<Text {...props} isEmoji letterSpacing={letterSpacing} lineHeight={lineHeight} size={size}>
{children || getEmoji(name)}
</Text>
);
}
1 change: 0 additions & 1 deletion src/helpers/RainbowContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) {
)}
{showSwitchModeButton && __DEV__ && (
<DevButton color={colors.dark} onPress={() => setTheme(isDarkMode ? 'light' : 'dark')}>
{/* @ts-expect-error ts-migrate(2741) FIXME: Property 'name' is missing in type... Remove this comment to see the full error message */}
<Emoji>{isDarkMode ? '🌞' : '🌚'}</Emoji>
</DevButton>
)}
Expand Down
9 changes: 2 additions & 7 deletions src/screens/ExternalLinkWarningSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useTheme } from '@/theme';
import { formatURLForDisplay } from '@/utils';
import { IS_ANDROID } from '@/env';

export const ExternalLinkWarningSheetHeight = 380 + (android ? 20 : 0);
export const ExternalLinkWarningSheetHeight = 380 + (IS_ANDROID ? 20 : 0);

const Container = styled(Centered).attrs({ direction: 'column' })(({ deviceHeight, height }) => ({
...position.coverAsObject,
Expand Down Expand Up @@ -52,12 +52,7 @@ const ExternalLinkWarningSheet = () => {
width: '100%',
}}
>
<Emoji
align="center"
size="h1"
style={{ ...fontWithWidth(fonts.weight.bold) }}
// @ts-expect-error JavaScript component
>
<Emoji align="center" size="h1" style={{ ...fontWithWidth(fonts.weight.bold) }}>
🧭
</Emoji>
<SheetTitle
Expand Down

0 comments on commit 91af6c1

Please sign in to comment.