Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New nft details page #10277

Merged
merged 58 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
e6c00d7
feat: New nft details page
sahar-fehri Jul 10, 2024
5b57547
fix: fix conflicts
sahar-fehri Jul 10, 2024
5b14624
fix: fix lint
sahar-fehri Jul 10, 2024
ce6585a
fix: unit test
sahar-fehri Jul 10, 2024
995e6b9
fix: fix merge conflicts
sahar-fehri Jul 10, 2024
1a2b538
fix: css in progress
sahar-fehri Jul 10, 2024
0eb02a2
fix: code fencing
sahar-fehri Jul 10, 2024
29be0a5
fix: css in progress ..
sahar-fehri Jul 10, 2024
87a3437
fix: css in progress
sahar-fehri Jul 11, 2024
4a0a32b
fix: add e2e test
sahar-fehri Jul 11, 2024
04f37e5
fix: add full image component
sahar-fehri Jul 12, 2024
17b326b
fix: fix locales
sahar-fehri Jul 12, 2024
ce8f4b6
fix: fix locales
sahar-fehri Jul 12, 2024
de99844
fix: fix
sahar-fehri Jul 12, 2024
f289284
fix: fix
sahar-fehri Jul 13, 2024
b9b3571
fix: use Icon component instead of deprecated components
sahar-fehri Jul 15, 2024
a683a2a
fix: merge conflicts
sahar-fehri Jul 15, 2024
bd8eadf
fix: fix value display
sahar-fehri Jul 16, 2024
03bc553
fix: fix date format
sahar-fehri Jul 16, 2024
a094e92
fix: fix patch merge conflicts
sahar-fehri Jul 16, 2024
e0b96a9
fix: fix date
sahar-fehri Jul 16, 2024
d12b26d
Merge branch 'main' into feat/NFT-details-new-design
sahar-fehri Jul 17, 2024
6e55d4b
fix: update patch types and fix image display
sahar-fehri Jul 18, 2024
7f3a037
fix: cleanup types and fix display when there is only contract address
sahar-fehri Jul 18, 2024
650b520
fix: fix snapshot
sahar-fehri Jul 18, 2024
6f5fbbd
fix: fix link
sahar-fehri Jul 18, 2024
5eab44c
fix: fix test
sahar-fehri Jul 18, 2024
727fe6a
fix: fix locales
sahar-fehri Jul 22, 2024
87571e1
fix: merge conflicts
sahar-fehri Jul 22, 2024
a452738
fix: lint
sahar-fehri Jul 22, 2024
a39a9a1
fix: fix
sahar-fehri Jul 22, 2024
8d8a662
fix: fix
sahar-fehri Jul 22, 2024
f4e73f9
fix: fix e2e
sahar-fehri Jul 22, 2024
b6f1f1c
fix: fix android test in progress ..
sahar-fehri Jul 22, 2024
425aff2
fix: update icons
sahar-fehri Jul 23, 2024
0bf29a0
update locator strategy to getElementByID to include an index. Also a…
cortisiko Jul 23, 2024
23d9fc6
fix: fix
sahar-fehri Jul 24, 2024
e1e5499
Merge branch 'main' into feat/NFT-details-new-design
sahar-fehri Jul 24, 2024
797dc0a
fix: add track events
sahar-fehri Jul 24, 2024
6a03eb4
fix: fix snapshot
sahar-fehri Jul 24, 2024
c8d41a7
fix: update e2e
sahar-fehri Jul 24, 2024
2785201
Merge branch 'main' into feat/NFT-details-new-design
sahar-fehri Jul 24, 2024
fcbf86d
fix: review feedback to refactor badge wrapper
sahar-fehri Jul 24, 2024
0551750
fix: remove unused style and rename
sahar-fehri Jul 24, 2024
a0f6ff5
fix: update snapshot
sahar-fehri Jul 24, 2024
58a7648
fix: fix e2e matcher
sahar-fehri Jul 25, 2024
4a6c25e
fix: update navbar color icons to default
sahar-fehri Jul 25, 2024
f5a7a88
fix: fix merge conflicts
sahar-fehri Jul 25, 2024
e0633fd
fix: add sheet design for long tokenId
sahar-fehri Jul 27, 2024
2c410cc
fix: fix conflicts
sahar-fehri Jul 27, 2024
75d1b32
fix: debounce navigation to nft details page
sahar-fehri Jul 30, 2024
b4beed8
fix: remove comment
sahar-fehri Jul 30, 2024
e4115c1
Merge branch 'main' into feat/NFT-details-new-design
sahar-fehri Jul 30, 2024
a56d65f
fix: fix import
sahar-fehri Jul 30, 2024
63719f9
Merge branch 'main' into feat/NFT-details-new-design
sahar-fehri Jul 30, 2024
7acd85c
fix: remove utils fcts and cleanup
sahar-fehri Jul 31, 2024
5075db2
Merge branch 'main' into feat/NFT-details-new-design
sahar-fehri Jul 31, 2024
47387d5
Merge branch 'main' into feat/NFT-details-new-design
sahar-fehri Aug 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
sahar-fehri marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
162 changes: 157 additions & 5 deletions app/components/Base/RemoteImage/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import React, { useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { Image, ViewPropTypes, View, StyleSheet } from 'react-native';
import {
Image,
ViewPropTypes,
View,
StyleSheet,
Dimensions,
} from 'react-native';
import FadeIn from 'react-native-fade-in-image';
// eslint-disable-next-line import/default
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
Expand All @@ -10,13 +16,44 @@ import ComponentErrorBoundary from '../../UI/ComponentErrorBoundary';
import useIpfsGateway from '../../hooks/useIpfsGateway';
import { getFormattedIpfsUrl } from '@metamask/assets-controllers';
import Identicon from '../../UI/Identicon';
import BadgeWrapper from '../../../component-library/components/Badges/BadgeWrapper';
import Badge, {
BadgeVariant,
} from '../../../component-library/components/Badges/Badge';
import { useSelector } from 'react-redux';
import {
selectChainId,
selectTicker,
} from '../../../selectors/networkController';
import {
getTestNetImageByChainId,
isLineaMainnet,
isMainNet,
isTestNet,
} from '../../../util/networks';
import images from 'images/image-icons';
import { selectNetworkName } from '../../../selectors/networkInfos';

import { BadgeAnchorElementShape } from '../../../component-library/components/Badges/BadgeWrapper/BadgeWrapper.types';
import useSvgUriViewBox from '../../hooks/useSvgUriViewBox';
import { AvatarSize } from '../../../component-library/components/Avatars/Avatar';

const createStyles = () =>
StyleSheet.create({
svgContainer: {
overflow: 'hidden',
},
badgeWrapper: {
flex: 1,
},
imageStyle: {
width: '100%',
height: '100%',
borderRadius: 8,
},
detailedImageStyle: {
borderRadius: 8,
},
});

const RemoteImage = (props) => {
Expand All @@ -26,6 +63,9 @@ const RemoteImage = (props) => {
const isImageUrl = isUrl(props?.source?.uri);
const ipfsGateway = useIpfsGateway();
const styles = createStyles();
const chainId = useSelector(selectChainId);
const ticker = useSelector(selectTicker);
const networkName = useSelector(selectNetworkName);
const resolvedIpfsUrl = useMemo(() => {
try {
const url = new URL(props.source.uri);
Expand All @@ -41,6 +81,51 @@ const RemoteImage = (props) => {

const onError = ({ nativeEvent: { error } }) => setError(error);

const [dimensions, setDimensions] = useState(null);

useEffect(() => {
const calculateImageDimensions = (imageWidth, imageHeight) => {
const deviceWidth = Dimensions.get('window').width;
const maxWidth = deviceWidth - 32;
const maxHeight = 0.75 * maxWidth;

if (imageWidth > imageHeight) {
// Horizontal image
const width = maxWidth;
const height = (imageHeight / imageWidth) * maxWidth;
return { width, height };
} else if (imageHeight > imageWidth) {
// Vertical image
const height = maxHeight;
const width = (imageWidth / imageHeight) * maxHeight;
return { width, height };
}
// Square image
return { width: maxHeight, height: maxHeight };
};

Image.getSize(
uri,
(width, height) => {
const { width: calculatedWidth, height: calculatedHeight } =
calculateImageDimensions(width, height);
setDimensions({ width: calculatedWidth, height: calculatedHeight });
},
() => {
console.error('Failed to get image dimensions');
},
);
}, [uri]);

const NetworkBadgeSource = () => {
if (isTestNet(chainId)) return getTestNetImageByChainId(chainId);

if (isMainNet(chainId)) return images.ETHEREUM;

if (isLineaMainnet(chainId)) return images['LINEA-MAINNET'];

return ticker ? images[ticker] : undefined;
};
const isSVG =
source &&
source.uri &&
Expand Down Expand Up @@ -83,12 +168,75 @@ const RemoteImage = (props) => {
}

if (props.fadeIn) {
const { style, ...restProps } = props;
const badge = {
top: -4,
right: -4,
};
return (
<FadeIn placeholderStyle={props.placeholderStyle}>
<Image {...props} source={{ uri }} onError={onError} />
</FadeIn>
<>
{props.isTokenImage ? (
<FadeIn placeholderStyle={props.placeholderStyle}>
<View>
{props.isFullRatio && dimensions ? (
<BadgeWrapper
badgePosition={badge}
anchorElementShape={BadgeAnchorElementShape.Rectangular}
badgeElement={
<Badge
variant={BadgeVariant.Network}
imageSource={NetworkBadgeSource()}
name={networkName}
isScaled={false}
size={AvatarSize.Md}
/>
}
>
<Image
source={{ uri }}
style={{
width: dimensions.width,
height: dimensions.height,
...styles.detailedImageStyle,
}}
/>
</BadgeWrapper>
) : (
<BadgeWrapper
badgePosition={badge}
anchorElementShape={BadgeAnchorElementShape.Rectangular}
badgeElement={
<Badge
variant={BadgeVariant.Network}
imageSource={NetworkBadgeSource()}
name={networkName}
isScaled={false}
size={AvatarSize.Xs}
/>
}
>
<View style={style}>
<Image
style={styles.imageStyle}
{...restProps}
source={{ uri }}
onError={onError}
resizeMode={'cover'}
/>
</View>
</BadgeWrapper>
)}
</View>
</FadeIn>
) : (
<FadeIn placeholderStyle={props.placeholderStyle}>
<Image {...props} source={{ uri }} onError={onError} />
</FadeIn>
)}
</>
);
}

return <Image {...props} source={{ uri }} onError={onError} />;
};

Expand Down Expand Up @@ -121,6 +269,10 @@ RemoteImage.propTypes = {
* Token address
*/
address: PropTypes.string,

isTokenImage: PropTypes.bool,

isFullRatio: PropTypes.bool,
};

export default RemoteImage;
8 changes: 8 additions & 0 deletions app/components/Nav/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ import BasicFunctionalityModal from '../../UI/BasicFunctionality/BasicFunctional
import SmartTransactionsOptInModal from '../../Views/SmartTransactionsOptInModal/SmartTranactionsOptInModal';
import ProfileSyncingModal from '../../UI/ProfileSyncing/ProfileSyncingModal/ProfileSyncingModal';
import NFTAutoDetectionModal from '../../../../app/components/Views/NFTAutoDetectionModal/NFTAutoDetectionModal';
import NftOptions from '../../../components/Views/NftOptions';
import ShowTokenIdSheet from '../../../components/Views/ShowTokenIdSheet';
import OriginSpamModal from '../../Views/OriginSpamModal/OriginSpamModal';
///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
import { SnapsExecutionWebView } from '../../../lib/snaps';
Expand Down Expand Up @@ -682,6 +684,7 @@ const App = ({ userLoggedIn }) => {
/>
<Stack.Screen name={'DetectedTokens'} component={DetectedTokensFlow} />
<Stack.Screen name={'AssetOptions'} component={AssetOptions} />
<Stack.Screen name={'NftOptions'} component={NftOptions} />
<Stack.Screen
name={Routes.MODAL.UPDATE_NEEDED}
component={UpdateNeeded}
Expand Down Expand Up @@ -715,6 +718,11 @@ const App = ({ userLoggedIn }) => {
name={Routes.MODAL.NFT_AUTO_DETECTION_MODAL}
component={NFTAutoDetectionModal}
/>
<Stack.Screen
name={Routes.SHEET.SHOW_TOKEN_ID}
component={ShowTokenIdSheet}
/>

<Stack.Screen
name={Routes.SHEET.ORIGIN_SPAM_MODAL}
component={OriginSpamModal}
Expand Down
34 changes: 34 additions & 0 deletions app/components/Nav/Main/MainNavigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ import ConfirmAddAsset from '../../UI/ConfirmAddAsset';
import { AesCryptoTestForm } from '../../Views/AesCryptoTestForm';
import { isTest } from '../../../util/test/utils';

import NftDetails from '../../Views/NftDetails';
import NftDetailsFullImage from '../../Views/NftDetails/NFtDetailsFullImage';

const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();

Expand Down Expand Up @@ -540,6 +543,32 @@ const SendView = () => (
</Stack.Navigator>
);

/* eslint-disable react/prop-types */
const NftDetailsModeView = (props) => (
<Stack.Navigator>
<Stack.Screen
name=" " // No name here because this title will be displayed in the header of the page
component={NftDetails}
initialParams={{
collectible: props.route.params?.collectible,
}}
/>
</Stack.Navigator>
);

/* eslint-disable react/prop-types */
const NftDetailsFullImageModeView = (props) => (
<Stack.Navigator>
<Stack.Screen
name=" " // No name here because this title will be displayed in the header of the page
component={NftDetailsFullImage}
initialParams={{
collectible: props.route.params?.collectible,
}}
/>
</Stack.Navigator>
);

const SendFlowView = () => (
<Stack.Navigator>
<Stack.Screen
Expand Down Expand Up @@ -722,6 +751,11 @@ const MainNavigator = () => (
name={Routes.NOTIFICATIONS.VIEW}
component={NotificationsModeView}
/>
<Stack.Screen name="NftDetails" component={NftDetailsModeView} />
<Stack.Screen
name="NftDetailsFullImage"
component={NftDetailsFullImageModeView}
/>
<Stack.Screen name={Routes.QR_SCANNER} component={QrScanner} />
<Stack.Screen name="PaymentRequestView" component={PaymentRequestView} />
<Stack.Screen name={Routes.RAMP.BUY}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { TextStyle, View } from 'react-native';
import ButtonLink from '../../../../component-library/components/Buttons/Button/variants/ButtonLink';
import { useStyles } from '../../../../component-library/hooks';
import Text, {
Expand All @@ -13,12 +13,14 @@ interface ContentDisplayProps {
content: string;
numberOfLines?: number;
disclaimer?: string;
textStyle?: TextStyle;
}

const ContentDisplay = ({
content,
numberOfLines = 3,
disclaimer,
textStyle,
}: ContentDisplayProps) => {
const { styles } = useStyles(styleSheet, {});

Expand All @@ -33,6 +35,7 @@ const ContentDisplay = ({
<Text
numberOfLines={isExpanded ? undefined : numberOfLines}
color={TextColor.Alternative}
style={[textStyle]}
>
{content}
</Text>
Expand Down
1 change: 1 addition & 0 deletions app/components/UI/CollectibleContractElement/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ function CollectibleContractElement({
style={styles.collectibleIcon}
collectible={{ ...collectible, name }}
onPressColectible={onPress}
isTokenImage
/>
</View>
</TouchableOpacity>
Expand Down
9 changes: 7 additions & 2 deletions app/components/UI/CollectibleContracts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { selectSelectedInternalAccountChecksummedAddress } from '../../../select
import { WalletViewSelectorsIDs } from '../../../../e2e/selectors/wallet/WalletView.selectors';
import { useMetrics } from '../../../components/hooks/useMetrics';
import { RefreshTestId, SpinnerTestId } from './constants';
import { debounce } from 'lodash';

const createStyles = (colors) =>
StyleSheet.create({
Expand Down Expand Up @@ -89,6 +90,10 @@ const createStyles = (colors) =>
},
});

const debouncedNavigation = debounce((navigation, collectible) => {
navigation.navigate('NftDetails', { collectible });
}, 200);

/**
* View that renders a list of CollectibleContract
* ERC-721 and ERC-1155
Expand Down Expand Up @@ -120,8 +125,8 @@ const CollectibleContracts = ({
networkType === MAINNET && !useNftDetection;

const onItemPress = useCallback(
(collectible, contractName) => {
navigation.navigate('CollectiblesDetails', { collectible, contractName });
(collectible) => {
debouncedNavigation(navigation, collectible);
},
[navigation],
);
Expand Down
Loading
Loading