diff --git a/src/components/change-wallet/AddressRow.tsx b/src/components/change-wallet/AddressRow.tsx
index b05b9daf9ca..a5af90c724b 100644
--- a/src/components/change-wallet/AddressRow.tsx
+++ b/src/components/change-wallet/AddressRow.tsx
@@ -13,7 +13,6 @@ import { Icon } from '../icons';
import { Centered, Column, ColumnWithMargins, Row } from '../layout';
import { Text, TruncatedText } from '../text';
import ContextMenuButton from '@/components/native-context-menu/contextMenu';
-import ContextMenuAndroid from '@/components/native-context-menu/contextMenu.android';
import useExperimentalFlag, { NOTIFICATIONS } from '@/config/experimentalHooks';
import {
removeFirstEmojiFromString,
@@ -25,6 +24,7 @@ import { abbreviations, deviceUtils, profileUtils } from '@/utils';
import { EditWalletContextMenuActions } from '@/screens/ChangeWalletSheet';
import { toChecksumAddress } from '@/handlers/web3';
import { IS_IOS, IS_ANDROID } from '@/env';
+import { ContextMenu } from '../context-menu';
const maxAccountLabelWidth = deviceUtils.dimensions.width - 88;
const NOOP = () => undefined;
@@ -219,6 +219,25 @@ export default function AddressRow({
menuTitle: walletName,
};
+ const handleSelectActionMenuItem = useCallback(
+ (buttonIndex: number) => {
+ switch (buttonIndex) {
+ case 0:
+ contextMenuActions?.edit(walletId, address);
+ break;
+ case 1:
+ contextMenuActions?.notifications(walletName, address);
+ break;
+ case 2:
+ contextMenuActions?.remove(walletId, address);
+ break;
+ default:
+ break;
+ }
+ },
+ [contextMenuActions, walletName, walletId, address]
+ );
+
const handleSelectMenuItem = useCallback(
// @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly
({ nativeEvent: { actionKey } }) => {
@@ -316,14 +335,15 @@ export default function AddressRow({
) : (
- // @ts-expect-error js component
- item.actionTitle)}
isAnchoredToRight
- onPressMenuItem={handleSelectMenuItem}
+ onPressActionSheet={handleSelectActionMenuItem}
>
-
-
+
+
+
+
))}
diff --git a/src/components/gas/GasSpeedButton.js b/src/components/gas/GasSpeedButton.js
index 4bff48b0017..79ff68abd30 100644
--- a/src/components/gas/GasSpeedButton.js
+++ b/src/components/gas/GasSpeedButton.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-nested-ternary */
/* eslint-disable no-undef */
import AnimateNumber from '@bankify/react-native-animate-number';
import lang from 'i18n-js';
@@ -34,6 +35,8 @@ import styled from '@/styled-thing';
import { fonts, fontWithWidth, margin, padding } from '@/styles';
import { ethereumUtils, gasUtils } from '@/utils';
import { getNetworkObj } from '@/networks';
+import { IS_ANDROID } from '@/env';
+import { ContextMenu } from '../context-menu';
const {
GAS_EMOJIS,
@@ -249,6 +252,7 @@ const GasSpeedButton = ({
shouldOpenCustomGasSheet.focusTo,
flashbotTransaction,
speeds,
+ fallbackColor,
]);
const openCustomOptions = useCallback(
@@ -377,6 +381,25 @@ const GasSpeedButton = ({
[handlePressSpeedOption]
);
+ const handlePressActionSheet = useCallback(
+ buttonIndex => {
+ switch (buttonIndex) {
+ case 0:
+ handlePressSpeedOption(NORMAL);
+ break;
+ case 1:
+ handlePressSpeedOption(FAST);
+ break;
+ case 2:
+ handlePressSpeedOption(URGENT);
+ break;
+ case 3:
+ handlePressSpeedOption(CUSTOM);
+ }
+ },
+ [handlePressSpeedOption]
+ );
+
const nativeFeeCurrency = useMemo(() => {
switch (currentNetwork) {
case networkTypes.polygon:
@@ -399,6 +422,8 @@ const GasSpeedButton = ({
const menuConfig = useMemo(() => {
const menuOptions = speedOptions.map(gasOption => {
+ if (IS_ANDROID) return gasOption;
+
const totalGwei = add(
gasFeeParamsBySpeed[gasOption]?.maxBaseFee?.gwei,
gasFeeParamsBySpeed[gasOption]?.maxPriorityFeePerGas?.gwei
@@ -441,6 +466,7 @@ const GasSpeedButton = ({
gasFeeParamsBySpeed,
selectedGasFeeOption,
speedOptions,
+ isL2,
]);
const gasOptionsAvailable = useMemo(() => speedOptions.length > 1, [
@@ -477,6 +503,24 @@ const GasSpeedButton = ({
/>
);
if (!gasOptionsAvailable || gasIsNotReady) return pager;
+
+ if (IS_ANDROID) {
+ return (
+
+ {pager}
+
+ );
+ }
+
return (
diff --git a/src/components/positions/PositionsCard.tsx b/src/components/positions/PositionsCard.tsx
index 5efdd314261..67ef48e329f 100644
--- a/src/components/positions/PositionsCard.tsx
+++ b/src/components/positions/PositionsCard.tsx
@@ -72,7 +72,7 @@ export const PositionCard = ({ position }: PositionCardProps) => {
}, [navigate, position]);
const depositTokens: CoinStackToken[] = useMemo(() => {
- let tokens: CoinStackToken[] = [];
+ const tokens: CoinStackToken[] = [];
position.deposits.forEach((deposit: RainbowDeposit) => {
deposit.underlying.forEach(({ asset }) => {
tokens.push({
diff --git a/src/screens/ENSIntroSheet.tsx b/src/screens/ENSIntroSheet.tsx
index 317ad38804f..e6252c7d81e 100644
--- a/src/screens/ENSIntroSheet.tsx
+++ b/src/screens/ENSIntroSheet.tsx
@@ -3,7 +3,7 @@ import { useRoute } from '@react-navigation/native';
import { IS_TESTING } from 'react-native-dotenv';
import lang from 'i18n-js';
import React, { useCallback, useMemo } from 'react';
-import { InteractionManager } from 'react-native';
+import { InteractionManager, View } from 'react-native';
import { MenuActionConfig } from 'react-native-ios-context-menu';
import LinearGradient from 'react-native-linear-gradient';
import ActivityIndicator from '../components/ActivityIndicator';
@@ -35,6 +35,8 @@ import {
} from '@/hooks';
import Routes from '@/navigation/routesNames';
import { useTheme } from '@/theme';
+import { IS_ANDROID } from '@/env';
+import ContextMenu from '@/components/context-menu/ContextMenu.android';
enum AnotherENSEnum {
search = 'search',
@@ -45,6 +47,92 @@ const topPadding = android ? 29 : 19;
const minHeight = 740;
+type ContextMenuRendererProps = {
+ children: React.ReactNode;
+ handleSelectExistingName: () => void;
+ handleNavigateToSearch: () => void;
+};
+
+const ContextMenuRenderer = ({
+ children,
+ handleSelectExistingName,
+ handleNavigateToSearch,
+}: ContextMenuRendererProps) => {
+ const menuConfig = useMemo(() => {
+ return {
+ menuItems: [
+ {
+ actionKey: AnotherENSEnum.my_ens,
+ actionTitle: lang.t('profiles.intro.my_ens_names'),
+ icon: {
+ iconType: 'SYSTEM',
+ iconValue: 'rectangle.stack.badge.person.crop',
+ },
+ },
+ {
+ actionKey: AnotherENSEnum.search,
+ actionTitle: lang.t('profiles.intro.search_new_ens'),
+ icon: {
+ iconType: 'SYSTEM',
+ iconValue: 'magnifyingglass',
+ },
+ },
+ ] as MenuActionConfig[],
+ menuTitle: '',
+ };
+ }, []);
+
+ const handlePressMenuItem = useCallback(
+ // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly
+ ({ nativeEvent: { actionKey } }) => {
+ if (actionKey === AnotherENSEnum.my_ens) {
+ handleSelectExistingName();
+ } else if (actionKey === AnotherENSEnum.search) {
+ handleNavigateToSearch();
+ }
+ },
+ [handleNavigateToSearch, handleSelectExistingName]
+ );
+
+ const handlePressActionSheet = useCallback(
+ (buttonIndex: number) => {
+ switch (buttonIndex) {
+ case 0:
+ handleSelectExistingName();
+ break;
+ case 1:
+ handleNavigateToSearch();
+ break;
+ }
+ },
+ [handleNavigateToSearch, handleSelectExistingName]
+ );
+
+ if (IS_ANDROID) {
+ return (
+ i.actionTitle)}
+ >
+ {children}
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+};
+
export default function ENSIntroSheet() {
const { width: deviceWidth, height: deviceHeight } = useDimensions();
const { colors } = useTheme();
@@ -64,6 +152,8 @@ export default function ENSIntroSheet() {
enabled: Boolean(uniqueDomain?.name),
});
+ console.log(nonPrimaryDomains.length);
+
// We are not using `isSmallPhone` from `useDimensions` here as we
// want to explicitly set a min height.
const isSmallPhone = deviceHeight < minHeight;
@@ -121,42 +211,6 @@ export default function ENSIntroSheet() {
});
}, [navigate, navigateToAssignRecords]);
- const menuConfig = useMemo(() => {
- return {
- menuItems: [
- {
- actionKey: AnotherENSEnum.my_ens,
- actionTitle: lang.t('profiles.intro.my_ens_names'),
- icon: {
- iconType: 'SYSTEM',
- iconValue: 'rectangle.stack.badge.person.crop',
- },
- },
- {
- actionKey: AnotherENSEnum.search,
- actionTitle: lang.t('profiles.intro.search_new_ens'),
- icon: {
- iconType: 'SYSTEM',
- iconValue: 'magnifyingglass',
- },
- },
- ] as MenuActionConfig[],
- menuTitle: '',
- };
- }, []);
-
- const handlePressMenuItem = useCallback(
- // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly
- ({ nativeEvent: { actionKey } }) => {
- if (actionKey === AnotherENSEnum.my_ens) {
- handleSelectExistingName();
- } else if (actionKey === AnotherENSEnum.search) {
- handleNavigateToSearch();
- }
- },
- [handleNavigateToSearch, handleSelectExistingName]
- );
-
return (
)}
{nonPrimaryDomains?.length > 0 ? (
-
-
+
) : (
void;
- contact?: Contact;
- account?: RainbowAccount;
-};
+type ContextMenuRendererProps = {
+ children: React.ReactNode;
+ name: string | undefined;
+ color: number | null;
+ formattedAddress: string | undefined;
+ fetchedEnsName: string | undefined;
+} & Omit;
-export const TransactionDetailsAddressRow: React.FC = ({
+const ContextMenuRenderer = ({
+ children,
+ account,
address,
- title,
- onAddressCopied,
contact,
- account,
-}) => {
- const formattedAddress = formatAddressForDisplay(address);
- const [fetchedEnsName, setFetchedEnsName] = useState();
- const [fetchedEnsImage, setFetchedEnsImage] = useState();
- const [imageLoaded, setImageLoaded] = useState(!!account?.image);
- const ensNameSharedValue = useTiming(!!fetchedEnsName, {
- duration: 420,
- easing: Easing.linear,
- });
-
- const accountEmoji = useMemo(() => returnStringFirstEmoji(account?.label), [
- account,
- ]);
- const accountName = useMemo(
- () => removeFirstEmojiFromString(account?.label),
- []
- );
- const color =
- account?.color ?? contact?.color ?? addressHashedColorIndex(address);
- const emoji = accountEmoji || addressHashedEmoji(address);
- const name =
- accountName || contact?.nickname || contact?.ens || formattedAddress;
-
- const imageUrl = fetchedEnsImage ?? account?.image;
- const ensAvatarSharedValue = useTiming(!!imageUrl && imageLoaded, {
- duration: account?.image ? 0 : 420,
- });
-
- useEffect(() => {
- if (!contact?.nickname && !accountName) {
- fetchReverseRecord(address).then(name => {
- if (name) {
- setFetchedEnsName(name);
- }
- });
- }
- }, []);
-
- useEffect(() => {
- if (!account?.image && (fetchedEnsName || contact?.ens)) {
- const ens = fetchedEnsName ?? contact?.ens;
- if (ens) {
- fetchENSAvatar(ens, { cacheFirst: true }).then(avatar => {
- if (avatar?.imageUrl) {
- setFetchedEnsImage(avatar.imageUrl);
- }
- });
- }
- }
- }, [fetchedEnsName]);
-
- const addressAnimatedStyle = useAnimatedStyle(() => ({
- opacity: interpolate(ensNameSharedValue.value, [0, 0.5, 1], [1, 0, 0]),
- }));
- const ensNameAnimatedStyle = useAnimatedStyle(() => ({
- opacity: interpolate(ensNameSharedValue.value, [0, 0.5, 1], [0, 0, 1]),
- }));
-
- const emojiAvatarAnimatedStyle = useAnimatedStyle(() => ({
- opacity: interpolate(ensAvatarSharedValue.value, [0, 1], [1, 0]),
- }));
- const ensAvatarAnimatedStyle = useAnimatedStyle(() => ({
- opacity: ensAvatarSharedValue.value,
- }));
-
+ name,
+ color,
+ formattedAddress,
+ fetchedEnsName,
+ onAddressCopied,
+}: ContextMenuRendererProps) => {
const menuConfig = useMemo(
() => ({
menuTitle: '',
@@ -151,7 +93,7 @@ export const TransactionDetailsAddressRow: React.FC = ({
},
],
}),
- [fetchedEnsName, address, contact, account]
+ [contact, account, formattedAddress, name]
);
const onPressMenuItem = useCallback(
@@ -193,18 +135,157 @@ export const TransactionDetailsAddressRow: React.FC = ({
return;
}
},
- [address, fetchedEnsName, contact]
+ [address, fetchedEnsName, contact, color, onAddressCopied]
);
- const onImageLoad = () => {
- setImageLoaded(true);
- };
+ const onPressActionSheetItem = useCallback(
+ (buttonIndex: number) => {
+ switch (buttonIndex) {
+ case 0:
+ Navigation.handleAction(Routes.SEND_FLOW, {
+ params: {
+ address,
+ },
+ screen: Routes.SEND_SHEET,
+ });
+ return;
+ case 1:
+ Navigation.handleAction(Routes.MODAL_SCREEN, {
+ address: address,
+ color: color,
+ contact,
+ ens: fetchedEnsName || contact?.ens,
+ type: 'contact_profile',
+ });
+ return;
+ case 2:
+ onAddressCopied?.();
+ haptics.notificationSuccess();
+ Clipboard.setString(address);
+ return;
+ }
+ },
+ [address, color, contact, fetchedEnsName, onAddressCopied]
+ );
+
+ if (IS_ANDROID) {
+ return (
+ i.actionTitle)}
+ >
+ {children}
+
+ );
+ }
return (
+ {children}
+
+ );
+};
+
+type Props = {
+ address: string;
+ title: string;
+ onAddressCopied?: () => void;
+ contact?: Contact;
+ account?: RainbowAccount;
+};
+
+export const TransactionDetailsAddressRow: React.FC = ({
+ address,
+ title,
+ onAddressCopied,
+ contact,
+ account,
+}) => {
+ const formattedAddress = formatAddressForDisplay(address);
+ const [fetchedEnsName, setFetchedEnsName] = useState();
+ const [fetchedEnsImage, setFetchedEnsImage] = useState();
+ const [imageLoaded, setImageLoaded] = useState(!!account?.image);
+ const ensNameSharedValue = useTiming(!!fetchedEnsName, {
+ duration: 420,
+ easing: Easing.linear,
+ });
+
+ const accountEmoji = useMemo(() => returnStringFirstEmoji(account?.label), [
+ account,
+ ]);
+ const accountName = useMemo(
+ () => removeFirstEmojiFromString(account?.label),
+ []
+ );
+ const color =
+ account?.color ?? contact?.color ?? addressHashedColorIndex(address);
+ const emoji = accountEmoji || addressHashedEmoji(address);
+ const name =
+ accountName || contact?.nickname || contact?.ens || formattedAddress;
+
+ const imageUrl = fetchedEnsImage ?? account?.image;
+ const ensAvatarSharedValue = useTiming(!!imageUrl && imageLoaded, {
+ duration: account?.image ? 0 : 420,
+ });
+
+ useEffect(() => {
+ if (!contact?.nickname && !accountName) {
+ fetchReverseRecord(address).then(name => {
+ if (name) {
+ setFetchedEnsName(name);
+ }
+ });
+ }
+ }, []);
+
+ useEffect(() => {
+ if (!account?.image && (fetchedEnsName || contact?.ens)) {
+ const ens = fetchedEnsName ?? contact?.ens;
+ if (ens) {
+ fetchENSAvatar(ens, { cacheFirst: true }).then(avatar => {
+ if (avatar?.imageUrl) {
+ setFetchedEnsImage(avatar.imageUrl);
+ }
+ });
+ }
+ }
+ }, [fetchedEnsName]);
+
+ const addressAnimatedStyle = useAnimatedStyle(() => ({
+ opacity: interpolate(ensNameSharedValue.value, [0, 0.5, 1], [1, 0, 0]),
+ }));
+ const ensNameAnimatedStyle = useAnimatedStyle(() => ({
+ opacity: interpolate(ensNameSharedValue.value, [0, 0.5, 1], [0, 0, 1]),
+ }));
+
+ const emojiAvatarAnimatedStyle = useAnimatedStyle(() => ({
+ opacity: interpolate(ensAvatarSharedValue.value, [0, 1], [1, 0]),
+ }));
+ const ensAvatarAnimatedStyle = useAnimatedStyle(() => ({
+ opacity: ensAvatarSharedValue.value,
+ }));
+
+ const onImageLoad = () => {
+ setImageLoaded(true);
+ };
+
+ return (
+
@@ -263,6 +344,6 @@ export const TransactionDetailsAddressRow: React.FC = ({
-
+
);
};