Skip to content

Commit 91af6c1

Browse files
authored
Fix address copy crash (and floating emojis animation) (#6392)
1 parent 73c782d commit 91af6c1

File tree

9 files changed

+150
-122
lines changed

9 files changed

+150
-122
lines changed

src/components/asset-list/RecyclerAssetList2/profile-header/ProfileActionButtonsRow.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,6 @@ export function CopyButton() {
238238

239239
return (
240240
<>
241-
{/* @ts-expect-error JavaScript component */}
242241
<CopyFloatingEmojis textToCopy={accountAddress}>
243242
<ActionButton onPress={handlePressCopy} icon="􀐅" testID="receive-button">
244243
{lang.t('wallet.copy')}

src/components/floating-emojis/CopyFloatingEmojis.js src/components/floating-emojis/CopyFloatingEmojis.tsx

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
1-
import React from 'react';
1+
import React, { FC, ReactNode } from 'react';
22
import { ButtonPressAnimation } from '../animations';
33
import FloatingEmojis from './FloatingEmojis';
44
import { useClipboard } from '@/hooks';
55
import { magicMemo } from '@/utils';
66

7-
const CopyFloatingEmojis = ({ children, disabled, onPress, textToCopy, ...props }) => {
7+
interface CopyFloatingEmojisProps {
8+
/** Child elements or nodes to render inside this component */
9+
children?: ReactNode;
10+
/** Whether the floating emojis and copy functionality is disabled */
11+
disabled?: boolean;
12+
/**
13+
* Callback to run on press.
14+
* Receives `textToCopy` (if provided) as an argument.
15+
*/
16+
onPress?: (textToCopy?: string) => void;
17+
/** The text that should be copied to the clipboard */
18+
textToCopy?: string;
19+
/** Any additional props you want to forward to FloatingEmojis */
20+
[key: string]: unknown;
21+
}
22+
23+
const CopyFloatingEmojis: FC<CopyFloatingEmojisProps> = ({ children, disabled = false, onPress, textToCopy, ...props }) => {
824
const { setClipboard } = useClipboard();
925

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

6-
const FloatingEmoji = ({
5+
interface FloatingEmojiProps {
6+
centerVertically?: boolean;
7+
disableHorizontalMovement?: boolean;
8+
disableVerticalMovement?: boolean;
9+
distance: number;
10+
duration: number;
11+
emoji: string;
12+
fadeOut?: boolean;
13+
index: number;
14+
left: number;
15+
marginTop?: number;
16+
opacityThreshold?: number;
17+
scaleTo: number;
18+
size: string;
19+
top?: number;
20+
wiggleFactor?: number;
21+
}
22+
23+
const FloatingEmoji: React.FC<FloatingEmojiProps> = ({
724
centerVertically,
825
disableHorizontalMovement,
926
disableVerticalMovement,
@@ -35,7 +52,7 @@ const FloatingEmoji = ({
3552

3653
const opacity = interpolate(
3754
progress,
38-
[0, distance * (opacityThreshold ?? 0.5), distance - size],
55+
[0, distance * (opacityThreshold ?? 0.5), distance - Number(size)],
3956
[1, fadeOut ? 0.89 : 1, fadeOut ? 0 : 1]
4057
);
4158

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

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

50-
/*
51-
We don't really know why these concrete numbers are used there.
52-
Original Author of these numbers: Mike Demarais
53-
*/
66+
// Horizontal movement
67+
const translateXComponentA = animation.value * Number(size) * everySecondEmojiMultiplier * everyThirdEmojiMultiplier;
68+
69+
// "Wiggle" calculations
5470
const wiggleMultiplierA = Math.sin(progress * (distance / 23.3));
5571
const wiggleMultiplierB = interpolate(
5672
progress,
5773
[0, distance / 10, distance],
58-
[10 * wiggleFactor, 6.9 * wiggleFactor, 4.2137 * wiggleFactor]
74+
[10 * (wiggleFactor ?? 1), 6.9 * (wiggleFactor ?? 1), 4.2137 * (wiggleFactor ?? 1)]
5975
);
6076
const translateXComponentB = wiggleMultiplierA * wiggleMultiplierB;
6177

6278
const translateX = disableHorizontalMovement ? 0 : translateXComponentA + translateXComponentB;
6379

80+
// Vertical movement
6481
const translateY = disableVerticalMovement ? 0 : -progress;
6582

6683
return {
6784
opacity,
6885
transform: [{ rotate }, { scale }, { translateX }, { translateY }],
6986
};
70-
}, []);
87+
});
7188

7289
return (
7390
<Animated.View
@@ -76,31 +93,16 @@ const FloatingEmoji = ({
7693
left,
7794
marginTop,
7895
position: 'absolute',
79-
top: centerVertically ? null : top || size * -0.5,
96+
top: centerVertically ? undefined : top ?? Number(size) * -0.5,
8097
},
8198
animatedStyle,
8299
]}
83100
>
84-
<Emoji name={emoji} size={size} />
101+
<Emoji name={emoji} />
85102
</Animated.View>
86103
);
87104
};
88-
FloatingEmoji.propTypes = {
89-
centerVertically: PropTypes.bool,
90-
disableHorizontalMovement: PropTypes.bool,
91-
disableVerticalMovement: PropTypes.bool,
92-
distance: PropTypes.number.isRequired,
93-
duration: PropTypes.number.isRequired,
94-
emoji: PropTypes.string.isRequired,
95-
fadeOut: PropTypes.bool,
96-
left: PropTypes.string.isRequired,
97-
marginTop: PropTypes.number,
98-
opacityThreshold: PropTypes.number,
99-
scaleTo: PropTypes.number.isRequired,
100-
size: PropTypes.string.isRequired,
101-
top: PropTypes.number,
102-
wiggleFactor: PropTypes.number,
103-
};
104105

105-
const neverRerender = () => true;
106+
const neverRerender = (): boolean => true;
107+
106108
export default React.memo(FloatingEmoji, neverRerender);

src/components/floating-emojis/FloatingEmojis.tsx

+41-46
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ import FloatingEmoji from './FloatingEmoji';
44
import GravityEmoji from './GravityEmoji';
55
import { useTimeout } from '@/hooks';
66
import { position } from '@/styles';
7-
import { DebugLayout } from '@/design-system';
8-
import { DEVICE_HEIGHT, DEVICE_WIDTH } from '@/utils/deviceUtils';
9-
import { AbsolutePortal } from '../AbsolutePortal';
107

118
interface Emoji {
129
emojiToRender: string;
@@ -101,49 +98,47 @@ const FloatingEmojis: React.FC<FloatingEmojisProps> = ({
10198
return (
10299
<View style={[{ zIndex: 1 }, style]} {...props}>
103100
{typeof children === 'function' ? children({ onNewEmoji }) : children}
104-
<AbsolutePortal>
105-
<Animated.View
106-
pointerEvents="none"
107-
style={{
108-
opacity,
109-
...position.coverAsObject,
110-
}}
111-
>
112-
{gravityEnabled
113-
? floatingEmojis.map(({ emojiToRender, x, y }, index) => (
114-
<GravityEmoji
115-
key={`${x}${y}`}
116-
distance={Math.ceil(distance)}
117-
duration={duration}
118-
emoji={emojiToRender}
119-
index={index}
120-
left={typeof size === 'number' ? x - size / 2 : x - Number(size) / 2}
121-
size={size}
122-
top={y}
123-
/>
124-
))
125-
: floatingEmojis.map(({ emojiToRender, x, y }, index) => (
126-
<FloatingEmoji
127-
centerVertically={centerVertically}
128-
disableHorizontalMovement={disableHorizontalMovement}
129-
disableVerticalMovement={disableVerticalMovement}
130-
distance={Math.ceil(distance)}
131-
duration={duration}
132-
emoji={emojiToRender}
133-
fadeOut={fadeOut}
134-
index={index}
135-
key={`${x}${y}`}
136-
left={`${x}`}
137-
marginTop={marginTop}
138-
opacityThreshold={opacityThreshold}
139-
scaleTo={scaleTo}
140-
size={`${size}`}
141-
top={y}
142-
wiggleFactor={wiggleFactor}
143-
/>
144-
))}
145-
</Animated.View>
146-
</AbsolutePortal>
101+
<Animated.View
102+
pointerEvents="none"
103+
style={{
104+
opacity,
105+
...position.coverAsObject,
106+
}}
107+
>
108+
{gravityEnabled
109+
? floatingEmojis.map(({ emojiToRender, x, y }, index) => (
110+
<GravityEmoji
111+
key={`${x}${y}`}
112+
distance={Math.ceil(distance)}
113+
duration={duration}
114+
emoji={emojiToRender}
115+
index={index}
116+
left={typeof size === 'number' ? x - size / 2 : x - Number(size) / 2}
117+
size={`${size}`}
118+
top={y}
119+
/>
120+
))
121+
: floatingEmojis.map(({ emojiToRender, x, y }, index) => (
122+
<FloatingEmoji
123+
centerVertically={centerVertically}
124+
disableHorizontalMovement={disableHorizontalMovement}
125+
disableVerticalMovement={disableVerticalMovement}
126+
distance={Math.ceil(distance)}
127+
duration={duration}
128+
emoji={emojiToRender}
129+
fadeOut={fadeOut}
130+
index={index}
131+
key={`${x}${y}`}
132+
left={x}
133+
marginTop={marginTop}
134+
opacityThreshold={opacityThreshold}
135+
scaleTo={scaleTo}
136+
size={`${size}`}
137+
top={y}
138+
wiggleFactor={wiggleFactor}
139+
/>
140+
))}
141+
</Animated.View>
147142
</View>
148143
);
149144
};

src/components/floating-emojis/GravityEmoji.tsx

+3-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface GravityEmojiProps {
88
emoji: string;
99
index: number;
1010
left: number;
11-
size: number;
11+
size: string;
1212
top: number;
1313
}
1414

@@ -74,16 +74,12 @@ const GravityEmoji = ({ distance, emoji, left, size, top }: GravityEmojiProps) =
7474
{
7575
left,
7676
position: 'absolute',
77-
top: top || size * -0.5,
77+
top: top || Number(size) * -0.5,
7878
},
7979
animatedStyle,
8080
]}
8181
>
82-
<Emoji
83-
name={emoji}
84-
// @ts-expect-error – JS component
85-
size={size}
86-
/>
82+
<Emoji name={emoji} size={size} />
8783
</Animated.View>
8884
);
8985
};

src/components/text/Emoji.js

-28
This file was deleted.

src/components/text/Emoji.tsx

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { isString } from 'lodash';
2+
import React, { ReactNode } from 'react';
3+
import Text from './Text';
4+
import { emojis } from '@/references';
5+
import { fonts } from '@/styles';
6+
7+
const emojiData = Object.entries(emojis).map(([emojiChar, { name }]) => {
8+
return [name, emojiChar];
9+
}) as [string, string][];
10+
11+
export const emoji = new Map<string, string>(emojiData);
12+
13+
function normalizeName(name: string): string {
14+
if (/:.+:/.test(name)) {
15+
name = name.slice(1, -1);
16+
}
17+
return name;
18+
}
19+
20+
function getEmoji(name: unknown): string | null {
21+
if (!isString(name)) return null;
22+
const result = emoji.get(normalizeName(name));
23+
return result ?? null;
24+
}
25+
26+
export interface TextProps {
27+
isEmoji?: boolean;
28+
letterSpacing?: string;
29+
lineHeight?: string;
30+
size?: keyof typeof fonts.size;
31+
[key: string]: unknown;
32+
}
33+
34+
export interface EmojiProps extends Omit<TextProps, 'isEmoji' | 'lineHeight' | 'letterSpacing' | 'children'> {
35+
children?: ReactNode;
36+
letterSpacing?: string;
37+
lineHeight?: string;
38+
name?: string;
39+
}
40+
41+
export default function Emoji({
42+
children = undefined,
43+
letterSpacing = 'zero',
44+
lineHeight = 'none',
45+
name,
46+
size = 'h4',
47+
...props
48+
}: EmojiProps) {
49+
return (
50+
<Text {...props} isEmoji letterSpacing={letterSpacing} lineHeight={lineHeight} size={size}>
51+
{children || getEmoji(name)}
52+
</Text>
53+
);
54+
}

src/helpers/RainbowContext.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ export default function RainbowContextWrapper({ children }: PropsWithChildren) {
9797
)}
9898
{showSwitchModeButton && __DEV__ && (
9999
<DevButton color={colors.dark} onPress={() => setTheme(isDarkMode ? 'light' : 'dark')}>
100-
{/* @ts-expect-error ts-migrate(2741) FIXME: Property 'name' is missing in type... Remove this comment to see the full error message */}
101100
<Emoji>{isDarkMode ? '🌞' : '🌚'}</Emoji>
102101
</DevButton>
103102
)}

src/screens/ExternalLinkWarningSheet.tsx

+2-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { useTheme } from '@/theme';
1414
import { formatURLForDisplay } from '@/utils';
1515
import { IS_ANDROID } from '@/env';
1616

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

1919
const Container = styled(Centered).attrs({ direction: 'column' })(({ deviceHeight, height }) => ({
2020
...position.coverAsObject,
@@ -52,12 +52,7 @@ const ExternalLinkWarningSheet = () => {
5252
width: '100%',
5353
}}
5454
>
55-
<Emoji
56-
align="center"
57-
size="h1"
58-
style={{ ...fontWithWidth(fonts.weight.bold) }}
59-
// @ts-expect-error JavaScript component
60-
>
55+
<Emoji align="center" size="h1" style={{ ...fontWithWidth(fonts.weight.bold) }}>
6156
🧭
6257
</Emoji>
6358
<SheetTitle

0 commit comments

Comments
 (0)