diff --git a/package.json b/package.json
index 8819aa39..a40e7a63 100644
--- a/package.json
+++ b/package.json
@@ -34,10 +34,10 @@
"@dfinity/principal": "^0.9.3",
"@hookform/error-message": "^2.0.0",
"@psychedelic/dab-js": "1.4.12",
- "@psychedelic/plug-controller": "0.24.9",
+ "@psychedelic/plug-controller": "0.25.0",
"@react-native-async-storage/async-storage": "^1.17.10",
+ "@react-native-clipboard/clipboard": "^1.11.1",
"@react-native-community/blur": "^4.2.0",
- "@react-native-community/clipboard": "^1.5.1",
"@react-native-masked-view/masked-view": "^0.2.7",
"@react-navigation/bottom-tabs": "^6.4.0",
"@react-navigation/elements": "^1.3.6",
diff --git a/src/components/common/AccountInfo/index.tsx b/src/components/common/AccountInfo/index.tsx
index 598ae126..ab851c2c 100644
--- a/src/components/common/AccountInfo/index.tsx
+++ b/src/components/common/AccountInfo/index.tsx
@@ -1,4 +1,4 @@
-import Clipboard from '@react-native-community/clipboard';
+import Clipboard from '@react-native-clipboard/clipboard';
import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
diff --git a/src/components/common/Copy/index.tsx b/src/components/common/Copy/index.tsx
index dbe4db39..6db428cf 100644
--- a/src/components/common/Copy/index.tsx
+++ b/src/components/common/Copy/index.tsx
@@ -1,4 +1,4 @@
-import Clipboard from '@react-native-community/clipboard';
+import Clipboard from '@react-native-clipboard/clipboard';
import { t } from 'i18next';
import React, { useEffect, useState } from 'react';
import { StyleProp, View, ViewStyle } from 'react-native';
diff --git a/src/components/common/Toast/index.tsx b/src/components/common/Toast/index.tsx
index 4348e8ef..1fc50083 100644
--- a/src/components/common/Toast/index.tsx
+++ b/src/components/common/Toast/index.tsx
@@ -23,7 +23,7 @@ export enum ToastTypes {
export interface ToastProps {
title: string;
- message: string;
+ message?: string;
type: 'success' | 'error' | 'info';
id: string;
}
@@ -65,9 +65,11 @@ function Toast({ title, message, type, id }: ToastProps) {
-
- {message}
-
+ {message && (
+
+ {message}
+
+ )}
);
}
diff --git a/src/components/common/Toast/styles.ts b/src/components/common/Toast/styles.ts
index f6082c75..6371aaf6 100644
--- a/src/components/common/Toast/styles.ts
+++ b/src/components/common/Toast/styles.ts
@@ -11,11 +11,11 @@ export default StyleSheet.create({
},
headerContainer: {
flexDirection: 'row',
- marginBottom: 8,
alignItems: 'center',
justifyContent: 'space-between',
},
message: {
+ marginTop: 8,
color: Colors.White.Pure,
opacity: 0.8,
},
diff --git a/src/components/common/Touchable/index.tsx b/src/components/common/Touchable/index.tsx
index 6355aa5a..d452c06a 100644
--- a/src/components/common/Touchable/index.tsx
+++ b/src/components/common/Touchable/index.tsx
@@ -16,7 +16,7 @@ import scales from '@/utils/animationScales';
interface Props {
children?: React.ReactNode;
- onPress?: () => void;
+ onPress?: (param?: any) => void;
onLongPress?: () => void;
hapticType?: HapticFeedbackTypes;
scale?: number;
diff --git a/src/components/formatters/UsdFormat/index.tsx b/src/components/formatters/UsdFormat/index.tsx
index 7245f61d..0d5b2400 100644
--- a/src/components/formatters/UsdFormat/index.tsx
+++ b/src/components/formatters/UsdFormat/index.tsx
@@ -9,9 +9,16 @@ interface Props {
style?: StyleProp;
decimalScale?: number;
suffix?: string;
+ numberOfLines?: number;
}
-function UsdFormat({ value, style, decimalScale = 2, suffix }: Props) {
+function UsdFormat({
+ value,
+ style,
+ decimalScale = 2,
+ suffix,
+ numberOfLines,
+}: Props) {
return (
textValue ? (
-
+
{textValue}
) : null
diff --git a/src/hooks/useCustomToast.ts b/src/hooks/useCustomToast.ts
index 74d1c996..009fef33 100644
--- a/src/hooks/useCustomToast.ts
+++ b/src/hooks/useCustomToast.ts
@@ -6,7 +6,7 @@ import { ToastTypes } from '@/components/common/Toast';
function useCustomToast() {
const toast = useToast();
- const showSuccess = useCallback((title: string, message: string) => {
+ const showSuccess = useCallback((title: string, message?: string) => {
toast.show(`${ToastTypes.success}-${title}`, {
data: {
type: ToastTypes.success,
@@ -16,7 +16,7 @@ function useCustomToast() {
});
}, []);
- const showError = useCallback((title: string, message: string) => {
+ const showError = useCallback((title: string, message?: string) => {
toast.show(`${ToastTypes.error}-${title}`, {
data: {
type: ToastTypes.error,
@@ -26,7 +26,7 @@ function useCustomToast() {
});
}, []);
- const showInfo = useCallback((title: string, message: string) => {
+ const showInfo = useCallback((title: string, message?: string) => {
toast.show(`${ToastTypes.info}-${title}`, {
data: {
type: ToastTypes.info,
diff --git a/src/interfaces/redux.ts b/src/interfaces/redux.ts
index 8cffb272..b4025e4a 100644
--- a/src/interfaces/redux.ts
+++ b/src/interfaces/redux.ts
@@ -1,5 +1,6 @@
import WalletConnect from '@walletconnect/client';
+import { Nullable } from './general';
import { ICNSData } from './icns';
import { WCWhiteListItem } from './walletConnect';
@@ -53,40 +54,20 @@ export interface CanisterInfo {
symbol?: string;
}
-interface Currency {
- symbol: string;
- decimals: number;
-}
-
-export interface TransactionDetails {
- status: string; //check if this is correct
- fee: {
- amount: string;
- currency: Currency;
- };
- from: string;
- amount: string;
- currency: Currency;
- to: string;
- caller: string;
-}
-
export interface Transaction {
- amount: string | number;
- type: string; //TODO: Add types here SEND/RECEIVE. Check ACTIVITY_TYPES
- symbol: string;
- hash: string;
+ type: string;
to: string;
from: string;
- date: Date;
- image: string;
- value?: string | number;
- status?: number | string;
- icon?: string;
- canisterId?: string;
- plug?: any;
- canisterInfo?: CanisterInfo;
- details?: TransactionDetails;
+ hash: string;
+ amount: Nullable;
+ value?: Nullable;
+ status: number;
+ date: bigint;
+ symbol: string;
+ logo: string;
+ canisterId: string;
+ details?: { [key: string]: any };
+ canisterInfo?: Object;
}
export interface Asset {
diff --git a/src/redux/slices/user.ts b/src/redux/slices/user.ts
index 2925f9f3..3996e439 100644
--- a/src/redux/slices/user.ts
+++ b/src/redux/slices/user.ts
@@ -2,7 +2,6 @@ import { Principal } from '@dfinity/principal';
import { Address } from '@psychedelic/plug-controller/dist/interfaces/contact_registry';
import { createAsyncThunk, createSlice, isAnyOf } from '@reduxjs/toolkit';
-import { JELLY_CANISTER_ID } from '@/constants/canister';
import { ENABLE_NFTS } from '@/constants/nfts';
import {
CollectionInfo,
@@ -35,7 +34,6 @@ import {
DEFAULT_TRANSACTION,
formatContact,
formatContactForController,
- formatTransaction,
TRANSACTION_STATUS,
} from '../utils';
@@ -184,25 +182,11 @@ export const getTransactions = createAsyncThunk<
try {
const { icpPrice } = params;
const instance = KeyRing.getInstance();
- const currentWalletId = instance?.currentWalletId;
- const state = await instance?.getState();
- const currentWallet = state.wallets[currentWalletId];
- const response = await instance?.getTransactions({});
- let parsedTrx =
- response?.transactions?.map(formatTransaction(icpPrice, currentWallet)) ||
- [];
-
- if (!ENABLE_NFTS) {
- parsedTrx = parsedTrx.filter(
- item =>
- !(
- item?.symbol === 'NFT' ||
- item?.details.canisterId === JELLY_CANISTER_ID
- )
- );
- }
+ const { transactions } = await instance?.getTransactions({
+ icpPrice,
+ });
- return parsedTrx;
+ return transactions;
} catch (e: any) {
return rejectWithValue(e.message);
}
diff --git a/src/redux/utils.js b/src/redux/utils.js
index f6758c68..2c2c184a 100644
--- a/src/redux/utils.js
+++ b/src/redux/utils.js
@@ -4,14 +4,11 @@ import Flatted from 'flatted';
import { t } from 'i18next';
import { TOKEN_IMAGES, TOKENS } from '@/constants/assets';
-import { ACTIVITY_STATUS } from '@/constants/business';
import {
KEYRING_KEYS_IN_STORAGE,
KEYRING_STORAGE_KEY,
} from '@/constants/keyring';
-import { formatAssetBySymbol, parseToFloatAmount } from '@/utils/currencies';
import { validateAccountId, validatePrincipalId } from '@/utils/ids';
-import { recursiveParseBigint } from '@/utils/objects';
import { clear } from './slices/keyring';
import {
@@ -82,113 +79,6 @@ export const getNewAccountData = async (dispatch, icpPrice) => {
dispatch(getContacts());
};
-const parseTransactionObject = transactionObject => {
- const { amount, currency, token, sonicData, canisterInfo } =
- transactionObject;
-
- const { decimals } = {
- ...currency,
- ...token,
- ...(sonicData?.token ?? {}),
- ...(canisterInfo?.tokenRegistryInfo?.details ?? {}),
- };
- // TODO: Decimals are currently not in DAB. Remove once they are added.
- const parsedAmount = parseToFloatAmount(
- amount,
- decimals || TOKENS[sonicData?.token?.details?.symbol]?.decimals
- );
-
- return {
- ...transactionObject,
- amount: parsedAmount,
- };
-};
-
-const parseTransaction = transaction => {
- const { details } = transaction;
- const { fee } = details;
-
- const parsedDetails = parseTransactionObject(details);
- let parsedFee = fee;
-
- if (fee instanceof Object && ('token' in fee || 'currency' in fee)) {
- parsedFee = parseTransactionObject(fee);
- }
-
- return {
- ...transaction,
- details: {
- ...parsedDetails,
- fee: parsedFee,
- },
- };
-};
-
-const getTransactionSymbol = details => {
- if (!details) {
- return '';
- }
- if ('tokenRegistryInfo' in (details?.canisterInfo || [])) {
- return details?.canisterInfo.tokenRegistryInfo.symbol;
- }
- if ('nftRegistryInfo' in (details?.canisterInfo || [])) {
- return 'NFT';
- }
- return (
- details?.currency?.symbol ??
- details?.sonicData?.token?.details?.symbol ??
- details?.details?.name ??
- ''
- );
-};
-
-const getTransactionType = (type, isOwnTx) => {
- if (!type) {
- return '';
- }
- if (type.includes('transfer')) {
- return isOwnTx ? 'SEND' : 'RECEIVE';
- }
- if (type.includes('Liquidity')) {
- return `${type.includes('removeLiquidity') ? 'Remove' : 'Add'} Liquidity`;
- }
- return type.toUpperCase();
-};
-
-export const formatTransaction = (icpPrice, currentWallet) => trx => {
- const { principal, accountId } = currentWallet;
-
- let parsedTransaction = recursiveParseBigint(parseTransaction(trx));
- const { details, hash, caller, timestamp } = parsedTransaction || {};
- const isOwnTx = [principal, accountId].includes(caller);
-
- const symbol = getTransactionSymbol(details);
- const asset = formatAssetBySymbol(details?.amount, symbol, icpPrice);
- const type = getTransactionType(parsedTransaction?.type, isOwnTx);
-
- const transaction = {
- amount: asset.amount,
- value: asset.value,
- icon: asset.icon,
- type,
- hash,
- to: details?.to?.icns || details?.to?.principal,
- from: details?.from?.icns || details?.from?.principal || caller,
- date: new Date(timestamp),
- status: ACTIVITY_STATUS[details?.status],
- image: details?.canisterInfo?.icon || TOKEN_IMAGES[symbol] || '',
- symbol,
- canisterId: details?.canisterId,
- plug: null,
- canisterInfo: details?.canisterInfo,
- details: {
- ...details,
- caller,
- },
- };
- return transaction;
-};
-
export const formatContact = contact => {
const [id] = Object.values(contact.value);
diff --git a/src/screens/flows/Deposit/components/IDDetails/index.js b/src/screens/flows/Deposit/components/IDDetails/index.js
index ff59355d..79282674 100644
--- a/src/screens/flows/Deposit/components/IDDetails/index.js
+++ b/src/screens/flows/Deposit/components/IDDetails/index.js
@@ -1,4 +1,4 @@
-import Clipboard from '@react-native-community/clipboard';
+import Clipboard from '@react-native-clipboard/clipboard';
import React, { useEffect, useState } from 'react';
import CopiedToast from '@/commonComponents/CopiedToast';
diff --git a/src/screens/flows/Send/index.tsx b/src/screens/flows/Send/index.tsx
index b7eaf18f..b75bf455 100644
--- a/src/screens/flows/Send/index.tsx
+++ b/src/screens/flows/Send/index.tsx
@@ -17,6 +17,7 @@ import { getICPPrice } from '@/redux/slices/icp';
import { sendToken, setTransaction, transferNFT } from '@/redux/slices/user';
import { formatCollections, FormattedCollection } from '@/utils/assets';
import {
+ isOwnAddress,
validateAccountId,
validateICNSName,
validatePrincipalId,
@@ -186,12 +187,9 @@ function Send({ route }: ScreenProps) {
}
const savedContact = contacts?.find(c => c.id === text);
- const isOwnAddress =
- text === currentWallet?.principal ||
- text === currentWallet?.accountId ||
- text === currentWallet?.icnsData?.reverseResolvedName;
+ const isOwn = isOwnAddress(text, currentWallet!);
- if (savedContact && !isOwnAddress) {
+ if (savedContact && !isOwn) {
setReceiver({
...savedContact,
isValid: true,
@@ -199,7 +197,7 @@ function Send({ route }: ScreenProps) {
scrollToTop();
} else {
const isValid =
- !isOwnAddress && (validatePrincipalId(text) || validateAccountId(text));
+ !isOwn && (validatePrincipalId(text) || validateAccountId(text));
setReceiver({ id: text, isValid });
}
};
diff --git a/src/screens/tabs/Profile/index.js b/src/screens/tabs/Profile/index.tsx
similarity index 72%
rename from src/screens/tabs/Profile/index.js
rename to src/screens/tabs/Profile/index.tsx
index beebb26a..053fafcb 100644
--- a/src/screens/tabs/Profile/index.js
+++ b/src/screens/tabs/Profile/index.tsx
@@ -1,20 +1,25 @@
-import { useNavigation, useScrollToTop } from '@react-navigation/native';
+import { useScrollToTop } from '@react-navigation/native';
import { FlashList } from '@shopify/flash-list';
-import React, { useRef } from 'react';
-import { useTranslation } from 'react-i18next';
+import { t } from 'i18next';
+import React, { useEffect, useRef, useState } from 'react';
import { RefreshControl, View } from 'react-native';
+import { Modalize } from 'react-native-modalize';
import { shallowEqual } from 'react-redux';
-import EmptyState from '@/commonComponents/EmptyState';
-import ErrorState from '@/commonComponents/ErrorState';
-import Header from '@/commonComponents/Header';
-import UserIcon from '@/commonComponents/UserIcon';
import Button from '@/components/buttons/Button';
-import { Touchable } from '@/components/common';
-import Text from '@/components/common/Text';
+import {
+ EmptyState,
+ ErrorState,
+ Header,
+ Text,
+ Touchable,
+ UserIcon,
+} from '@/components/common';
import Icon from '@/components/icons';
import { ERROR_TYPES } from '@/constants/general';
import { Colors } from '@/constants/theme';
+import { ScreenProps } from '@/interfaces/navigation';
+import { Transaction } from '@/interfaces/redux';
import { Container, Separator } from '@/layout';
import Routes from '@/navigation/Routes';
import { useAppDispatch, useAppSelector } from '@/redux/hooks';
@@ -25,29 +30,43 @@ import ActivityItem, {
import animationScales from '@/utils/animationScales';
import Accounts from './modals/Accounts';
+import ActivityDetail from './modals/ActivityDetail';
import styles from './styles';
-const Profile = () => {
- const { t } = useTranslation();
- const navigation = useNavigation();
- const dispatch = useAppDispatch();
- const modalRef = useRef(null);
+function Profile({ navigation }: ScreenProps) {
+ const accountsModalRef = useRef(null);
+ const activityDetailModalRef = useRef(null);
const transactionListRef = useRef(null);
+ const [selectedTransaction, setSelectedTransaction] = useState();
+
useScrollToTop(transactionListRef);
+ const dispatch = useAppDispatch();
const reverseResolvedName = useAppSelector(
state => state.keyring.currentWallet?.icnsData?.reverseResolvedName
);
-
const { currentWallet } = useAppSelector(state => state.keyring);
const { icpPrice } = useAppSelector(state => state.icp);
const { transactions, transactionsLoading, transactionsError } =
useAppSelector(state => state.user, shallowEqual);
+ useEffect(() => {
+ if (selectedTransaction) {
+ activityDetailModalRef.current?.open();
+ }
+ }, [selectedTransaction]);
+
const onRefresh = () => {
dispatch(getTransactions({ icpPrice }));
};
- const renderTransaction = ({ item }) => ;
+ const renderTransaction = ({ item }: { item: Transaction }) => (
+ {
+ setSelectedTransaction(item);
+ }}
+ />
+ );
return (
<>
@@ -66,7 +85,7 @@ const Profile = () => {
{
text={t('common.change')}
buttonStyle={styles.buttonStyle}
textStyle={styles.buttonTextStyle}
- onPress={modalRef.current?.open}
+ onPress={() => accountsModalRef.current?.open()}
/>
@@ -120,9 +139,14 @@ const Profile = () => {
/>
)}
-
+
+ setSelectedTransaction(undefined)}
+ />
>
);
-};
+}
export default Profile;
diff --git a/src/screens/tabs/Profile/modals/Accounts/index.tsx b/src/screens/tabs/Profile/modals/Accounts/index.tsx
index 4135defc..282f5859 100644
--- a/src/screens/tabs/Profile/modals/Accounts/index.tsx
+++ b/src/screens/tabs/Profile/modals/Accounts/index.tsx
@@ -1,4 +1,4 @@
-import Clipboard from '@react-native-community/clipboard';
+import Clipboard from '@react-native-clipboard/clipboard';
import { t } from 'i18next';
import React, { RefObject, useEffect, useRef, useState } from 'react';
import { ActivityIndicator, Alert, Platform, View } from 'react-native';
diff --git a/src/screens/tabs/Profile/modals/ActivityDetail/index.tsx b/src/screens/tabs/Profile/modals/ActivityDetail/index.tsx
new file mode 100644
index 00000000..6cc0f653
--- /dev/null
+++ b/src/screens/tabs/Profile/modals/ActivityDetail/index.tsx
@@ -0,0 +1,133 @@
+import Clipboard from '@react-native-clipboard/clipboard';
+import { t } from 'i18next';
+import React, { RefObject } from 'react';
+import { View } from 'react-native';
+import { Modalize } from 'react-native-modalize';
+
+import {
+ ActionButton,
+ Header,
+ Modal,
+ Text,
+ Touchable,
+} from '@/components/common';
+import useCustomToast from '@/hooks/useCustomToast';
+import { Contact, Transaction } from '@/interfaces/redux';
+import { useAppSelector } from '@/redux/hooks';
+import ActivityItem from '@/screens/tabs/components/ActivityItem';
+import { isOwnAddress, validateICNSName } from '@/utils/ids';
+import shortAddress from '@/utils/shortAddress';
+import { capitalize } from '@/utils/strings';
+
+import styles from './styles';
+
+interface Props {
+ modalRef: RefObject;
+ activity: Transaction;
+ onClosed: () => void;
+}
+
+interface RowProps {
+ title: string;
+ value: string;
+ onPress?: (address: string) => void;
+ showSelf?: boolean;
+ hideRow?: boolean;
+ contact?: Contact;
+}
+
+const formatAddress = (address: string) =>
+ validateICNSName(address)
+ ? address
+ : shortAddress(address, {
+ leftSize: 5,
+ rightSize: 9,
+ separator: '...',
+ replace: [],
+ });
+
+function ActivityDetail({ modalRef, activity, onClosed }: Props) {
+ const { currentWallet } = useAppSelector(state => state.keyring);
+ const { contacts } = useAppSelector(state => state.user);
+ const { showInfo } = useCustomToast();
+
+ const closeModal = () => modalRef?.current?.close();
+ const handleOnCopy = (address: string) => () => {
+ Clipboard.setString(address);
+ showInfo(t('activity.details.copied'));
+ };
+
+ const ROWS =
+ activity && currentWallet
+ ? [
+ {
+ title: t('activity.details.trxType'),
+ value: capitalize(activity.type?.toLocaleLowerCase()),
+ },
+ {
+ title: t('activity.details.from'),
+ value: formatAddress(activity.from),
+ onPress: handleOnCopy(activity.from),
+ showSelf: isOwnAddress(activity.from, currentWallet),
+ hideRow: activity.type === 'MINT',
+ contact: contacts.find(c => c.id === activity.from),
+ },
+ {
+ title: t('activity.details.to'),
+ value: formatAddress(activity.to),
+ onPress: handleOnCopy(activity.to),
+ showSelf: isOwnAddress(activity.to, currentWallet),
+ contact: contacts.find(c => c.id === activity.to),
+ },
+ ]
+ : [];
+
+ const renderRow = (
+ { onPress, hideRow, title, value, showSelf, contact }: RowProps,
+ index: number
+ ) => {
+ const isTouchable = !!onPress;
+
+ return !hideRow ? (
+
+
+ {title}
+
+
+
+ {value}
+ {(showSelf || !!contact) && (
+
+ {showSelf ? t('activity.details.you') : ` (${contact?.name})`}
+
+ )}
+
+
+
+ ) : null;
+ };
+
+ return (
+
+ }
+ center={{t('activity.details.title')}}
+ />
+ }>
+
+
+ {ROWS.map(renderRow)}
+
+
+ );
+}
+
+export default ActivityDetail;
diff --git a/src/screens/tabs/Profile/modals/ActivityDetail/styles.ts b/src/screens/tabs/Profile/modals/ActivityDetail/styles.ts
new file mode 100644
index 00000000..444ff4a8
--- /dev/null
+++ b/src/screens/tabs/Profile/modals/ActivityDetail/styles.ts
@@ -0,0 +1,36 @@
+import { StyleSheet } from 'react-native';
+
+import { Colors } from '@/constants/theme';
+
+const ITEM_HEIGHT = 95;
+
+export default StyleSheet.create({
+ container: {
+ flex: 1,
+ paddingHorizontal: 20,
+ },
+ activityItem: {
+ paddingHorizontal: 0,
+ borderTopWidth: 1,
+ borderBottomWidth: 1,
+ borderColor: Colors.Divider[1],
+ height: ITEM_HEIGHT,
+ marginBottom: 24,
+ },
+ row: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ rowTitle: {
+ color: Colors.Gray.Pure,
+ marginBottom: 24,
+ },
+ rowValue: {
+ color: Colors.White.Primary,
+ marginBottom: 24,
+ },
+ link: {
+ color: Colors.ActionBlue,
+ },
+});
diff --git a/src/screens/tabs/Profile/screens/Contacts/index.tsx b/src/screens/tabs/Profile/screens/Contacts/index.tsx
index 1197cfba..3b697229 100644
--- a/src/screens/tabs/Profile/screens/Contacts/index.tsx
+++ b/src/screens/tabs/Profile/screens/Contacts/index.tsx
@@ -1,4 +1,4 @@
-import Clipboard from '@react-native-community/clipboard';
+import Clipboard from '@react-native-clipboard/clipboard';
import { t } from 'i18next';
import React, { Fragment, useMemo, useRef, useState } from 'react';
import { Platform, RefreshControl, View } from 'react-native';
diff --git a/src/screens/tabs/Tokens/index.js b/src/screens/tabs/Tokens/index.js
index f4547d73..e1eaa51e 100644
--- a/src/screens/tabs/Tokens/index.js
+++ b/src/screens/tabs/Tokens/index.js
@@ -1,4 +1,4 @@
-import Clipboard from '@react-native-community/clipboard';
+import Clipboard from '@react-native-clipboard/clipboard';
import { useScrollToTop } from '@react-navigation/native';
import { t } from 'i18next';
import React, { useMemo, useRef, useState } from 'react';
diff --git a/src/screens/tabs/components/ActivityIcon/index.js b/src/screens/tabs/components/ActivityIcon/index.js
deleted file mode 100644
index 32ce23ab..00000000
--- a/src/screens/tabs/components/ActivityIcon/index.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-import { Image, StyleSheet, View } from 'react-native';
-
-import Icon from '@/components/icons';
-import { ACTIVITY_IMAGES } from '@/constants/business';
-
-import { parseImageName } from '../utils';
-import styles from './styles';
-
-const ActivityIcon = ({ image, type }) => {
- return (
-
- {type && (
-
- )}
- {image?.includes('http') ? (
-
- ) : (
-
- )}
-
- );
-};
-
-export default ActivityIcon;
diff --git a/src/screens/tabs/components/ActivityIcon/index.tsx b/src/screens/tabs/components/ActivityIcon/index.tsx
new file mode 100644
index 00000000..b20d4c9d
--- /dev/null
+++ b/src/screens/tabs/components/ActivityIcon/index.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import { StyleSheet, View } from 'react-native';
+
+import { Image } from '@/components/common';
+import Icon from '@/components/icons';
+
+import { getNativeTokensLogo, getTypeIcon } from '../utils';
+import styles from './styles';
+
+interface Props {
+ logo?: string;
+ type?: string;
+ symbol: string;
+}
+
+const ActivityIcon = ({ logo, type, symbol }: Props) => {
+ return (
+
+ {type && }
+ {logo ? (
+
+ ) : (
+
+ )}
+
+ );
+};
+
+export default ActivityIcon;
diff --git a/src/screens/tabs/components/ActivityIcon/styles.js b/src/screens/tabs/components/ActivityIcon/styles.ts
similarity index 100%
rename from src/screens/tabs/components/ActivityIcon/styles.js
rename to src/screens/tabs/components/ActivityIcon/styles.ts
diff --git a/src/screens/tabs/components/ActivityItem/index.js b/src/screens/tabs/components/ActivityItem/index.js
deleted file mode 100644
index 5626d3a6..00000000
--- a/src/screens/tabs/components/ActivityItem/index.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-import { View } from 'react-native';
-
-import Text from '@/components/common/Text';
-import { VISIBLE_DECIMALS } from '@/constants/business';
-import { FontStyles } from '@/constants/theme';
-import UsdFormat from '@/formatters/UsdFormat';
-import { formatDate } from '@/utils/dates';
-import { formatToMaxDecimals } from '@/utils/number';
-import shortAddress from '@/utils/shortAddress';
-
-import ActivityIcon from '../ActivityIcon';
-import { getCanisterName, getStatus, getSubtitle, getTitle } from '../utils';
-import styles, { HEIGHT } from './styles';
-
-export const ITEM_HEIGHT = HEIGHT;
-
-const ActivityItem = ({
- type,
- to,
- from,
- amount,
- value,
- status,
- date,
- plug,
- swapData,
- symbol,
- image,
- canisterId,
- details,
- canisterInfo,
-}) => {
- const { t } = useTranslation();
- const isSonic = !!details?.sonicData;
- const isSwap = type === 'SWAP';
- const isLiquidity = type.includes('Liquidity');
-
- return (
-
-
-
-
- {getTitle(type, symbol, swapData, plug)}
-
-
- {getStatus(status, styles)}
- {formatDate(date, 'MMM Do')}
- {getSubtitle(type, to, from, canisterId)}
-
-
-
- {details?.tokenId && !isSonic ? (
- <>
-
- {details?.tokenId?.length > 5
- ? shortAddress(details?.tokenId)
- : `#${details?.tokenId}`}
-
-
- {getCanisterName(canisterInfo, canisterId)}
-
- >
- ) : isSwap || isLiquidity ? (
- {t('common.comingSoon')}
- ) : (
- <>
- {amount ? (
- {`${formatToMaxDecimals(
- Number(amount),
- VISIBLE_DECIMALS
- )} ${symbol}`}
- ) : null}
- {value ? (
-
- ) : null}
- >
- )}
-
-
- );
-};
-
-export default ActivityItem;
diff --git a/src/screens/tabs/components/ActivityItem/index.tsx b/src/screens/tabs/components/ActivityItem/index.tsx
new file mode 100644
index 00000000..533b6fca
--- /dev/null
+++ b/src/screens/tabs/components/ActivityItem/index.tsx
@@ -0,0 +1,93 @@
+import { t } from 'i18next';
+import React from 'react';
+import { StyleProp, View, ViewStyle } from 'react-native';
+
+import { Text, Touchable } from '@/components/common';
+import { VISIBLE_DECIMALS } from '@/constants/business';
+import { FontStyles } from '@/constants/theme';
+import UsdFormat from '@/formatters/UsdFormat';
+import { Transaction } from '@/interfaces/redux';
+import { formatDate } from '@/utils/dates';
+import { formatToMaxDecimals } from '@/utils/number';
+import shortAddress from '@/utils/shortAddress';
+
+import ActivityIcon from '../ActivityIcon';
+import { getCanisterName, getStatus, getTitle } from '../utils';
+import styles, { HEIGHT } from './styles';
+
+export const ITEM_HEIGHT = HEIGHT;
+
+interface Props extends Transaction {
+ onPress?: (trx: Transaction) => void;
+ style?: StyleProp;
+ hideAddress?: boolean;
+}
+
+const ActivityItem = ({
+ type,
+ amount,
+ value,
+ status,
+ date,
+ symbol,
+ logo,
+ canisterId,
+ details,
+ style,
+ canisterInfo,
+ onPress,
+}: Props) => {
+ const isSonic = !!details?.sonicData;
+ const isLiquidity = type.includes('Liquidity');
+
+ return (
+
+
+
+
+
+ {getTitle(type, symbol)}
+
+
+ {getStatus(status, styles)}
+ {formatDate(date, 'MMM Do')}
+
+
+
+ {details?.tokenId && !isSonic ? (
+ <>
+
+ {details?.tokenId?.length > 5
+ ? shortAddress(details?.tokenId)
+ : `#${details?.tokenId}`}
+
+
+ {getCanisterName(canisterInfo, canisterId)}
+
+ >
+ ) : isLiquidity ? (
+ {t('common.comingSoon')}
+ ) : (
+ <>
+ {amount ? (
+ {`${formatToMaxDecimals(
+ Number(amount),
+ VISIBLE_DECIMALS
+ )} ${symbol}`}
+ ) : null}
+ {value ? (
+
+ ) : null}
+ >
+ )}
+
+
+
+ );
+};
+
+export default ActivityItem;
diff --git a/src/screens/tabs/components/ActivityItem/styles.js b/src/screens/tabs/components/ActivityItem/styles.ts
similarity index 82%
rename from src/screens/tabs/components/ActivityItem/styles.js
rename to src/screens/tabs/components/ActivityItem/styles.ts
index c51dee38..f67bf498 100644
--- a/src/screens/tabs/components/ActivityItem/styles.js
+++ b/src/screens/tabs/components/ActivityItem/styles.ts
@@ -14,17 +14,21 @@ export default StyleSheet.create({
},
leftContainer: {
justifyContent: 'space-evenly',
+ maxWidth: '50%',
},
rightContainer: {
marginLeft: 'auto',
alignItems: 'flex-end',
justifyContent: 'space-evenly',
- },
- canisterName: {
- maxWidth: '50%',
+ paddingLeft: 5,
+ maxWidth: '32%',
},
title: {
- maxWidth: '78%',
+ width: '100%',
...FontStyles.Normal,
},
+ text: {
+ ...FontStyles.SmallGray,
+ width: '100%',
+ },
});
diff --git a/src/screens/tabs/components/utils.js b/src/screens/tabs/components/utils.js
index 48531a02..fae1cc6c 100644
--- a/src/screens/tabs/components/utils.js
+++ b/src/screens/tabs/components/utils.js
@@ -2,25 +2,45 @@ import { t } from 'i18next';
import React from 'react';
import Text from '@/components/common/Text';
-import { ACTIVITY_STATUS } from '@/constants/business';
+import { TOKENS } from '@/constants/assets';
+import {
+ ACTIVITY_IMAGES,
+ ACTIVITY_STATUS,
+ ACTIVITY_TYPES,
+} from '@/constants/business';
import { JELLY_CANISTER_ID } from '@/constants/canister';
-import { validateICNSName } from '@/utils/ids';
-import shortAddress from '@/utils/shortAddress';
import { capitalize } from '@/utils/strings.js';
-export const parseImageName = name => name.replace('.png', '').toLowerCase();
+export const getNativeTokensLogo = symbol => {
+ switch (symbol) {
+ case TOKENS.ICP.symbol:
+ return TOKENS.ICP.icon;
+ case TOKENS.WICP.symbol:
+ return TOKENS.WICP.icon;
+ case TOKENS.XTC.symbol:
+ return TOKENS.XTC.icon;
+ default:
+ return 'unknown';
+ }
+};
-export const getTitle = (type, symbol, swapData, plug) => {
+export const getTypeIcon = type => {
+ switch (type) {
+ case ACTIVITY_TYPES.RECEIVE:
+ return ACTIVITY_IMAGES.RECEIVE;
+ case ACTIVITY_TYPES.BURN:
+ return ACTIVITY_IMAGES.BURN;
+ case ACTIVITY_TYPES.SEND:
+ return ACTIVITY_IMAGES.SEND;
+ case ACTIVITY_TYPES.MINT:
+ return ACTIVITY_IMAGES.MINT;
+ default:
+ return 'actionActivity';
+ }
+};
+
+export const getTitle = (type, symbol) => {
switch (type) {
- case 'SWAP':
- return swapData?.currency.name
- ? t('transactionTypes.swapFor', {
- from: symbol,
- to: swapData?.currency.name,
- })
- : t('transactionTypes.swap');
- case 'PLUG':
- return t('common.pluggedInto', { name: plug.name });
case 'DIRECTBUY':
return t('transactionTypes.buyNTF');
case 'MAKELISTING':
@@ -63,21 +83,6 @@ export const getStatus = (status, styles) => {
}
};
-export const getSubtitle = (type, to, from) => {
- const toText = t('activity.subtitleTo', {
- value: validateICNSName(to) ? to : shortAddress(to),
- });
- const fromText = t('activity.subtitleFrom', {
- value: validateICNSName(from) ? from : shortAddress(from),
- });
-
- return {
- SEND: toText,
- BURN: toText,
- RECEIVE: fromText,
- }[type];
-};
-
export const getCanisterName = (canisterInfo, canisterId) => {
// TODO: change this when jelly supports multi-collections
if (canisterId === JELLY_CANISTER_ID) {
diff --git a/src/translations/en/index.js b/src/translations/en/index.js
index 7981f400..0484951b 100644
--- a/src/translations/en/index.js
+++ b/src/translations/en/index.js
@@ -141,6 +141,14 @@ const translations = {
},
},
activity: {
+ details: {
+ title: 'Activity Detail',
+ trxType: 'Transaction Type:',
+ from: 'From:',
+ to: 'To:',
+ you: ' (you)',
+ copied: 'Address copied in clipboard',
+ },
[ACTIVITY_STATUS.COMPLETED]: 'Completed',
[ACTIVITY_STATUS.PENDING]: 'Pending',
[ACTIVITY_STATUS.REVERTED]: 'Failed',
diff --git a/src/utils/ids.ts b/src/utils/ids.ts
index d93397f4..9782a2d9 100644
--- a/src/utils/ids.ts
+++ b/src/utils/ids.ts
@@ -6,6 +6,7 @@ import {
CANISTER_MAX_LENGTH,
ICNS_REGEX,
} from '@/constants/addresses';
+import { Wallet } from '@/interfaces/redux';
export const validateICNSName = (name: string) => ICNS_REGEX.test(name);
@@ -27,3 +28,9 @@ export const validateCanisterId = (text: string) => {
return false;
}
};
+
+export const isOwnAddress = (address: string, currentWallet: Wallet) =>
+ validateICNSName(address)
+ ? address === currentWallet.icnsData?.reverseResolvedName
+ : address === currentWallet.principal ||
+ address === currentWallet.accountId;
diff --git a/yarn.lock b/yarn.lock
index 71424b49..9afe4b18 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1828,10 +1828,10 @@
cross-fetch "^3.1.4"
crypto-js "^4.1.1"
-"@psychedelic/plug-controller@0.24.9":
- version "0.24.9"
- resolved "https://npm.pkg.github.com/download/@Psychedelic/plug-controller/0.24.9/ef37d22d00554de71b00782dbd7a440c2c9ee6f6#ef37d22d00554de71b00782dbd7a440c2c9ee6f6"
- integrity sha512-PJd5oN+b30VOkjfge6sIqpN9K2w9Xawcc2Tw+ByAXIpLFNVUPOmcOm2LOuMgPo++xtlqAonGsDAGqQ4KUq/4Zw==
+"@psychedelic/plug-controller@0.25.0":
+ version "0.25.0"
+ resolved "https://npm.pkg.github.com/download/@Psychedelic/plug-controller/0.25.0/44dd9d6b554615d07390d389f397fbe8a65f8ebd#44dd9d6b554615d07390d389f397fbe8a65f8ebd"
+ integrity sha512-4o1k3Z698JraEuN++d5l+vGPf11ax5m0PXvRk99Bn/i6uQ9KQD09HYF/mA3dF8++stnHqgtC1XCTID2wFIXJEg==
dependencies:
"@dfinity/agent" "0.9.3"
"@dfinity/candid" "0.9.3"
@@ -1868,6 +1868,11 @@
dependencies:
merge-options "^3.0.4"
+"@react-native-clipboard/clipboard@^1.11.1":
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/@react-native-clipboard/clipboard/-/clipboard-1.11.1.tgz#d3a9e685ce2383b1e92b89a334896c5575cc103d"
+ integrity sha512-nvSIIHzybVWqYxcJE5hpT17ekxAAg383Ggzw5WrYHtkKX61N1AwaKSNmXs5xHV7pmKSOe/yWjtSwxIzfW51I5Q==
+
"@react-native-community/blur@^4.2.0":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@react-native-community/blur/-/blur-4.2.0.tgz#f100d0ba220ecfed26be3c0ad2ceffa5eee17533"
@@ -2058,11 +2063,6 @@
prompts "^2.4.0"
semver "^6.3.0"
-"@react-native-community/clipboard@^1.5.1":
- version "1.5.1"
- resolved "https://registry.yarnpkg.com/@react-native-community/clipboard/-/clipboard-1.5.1.tgz#32abb3ea2eb91ee3f9c5fb1d32d5783253c9fabe"
- integrity sha512-AHAmrkLEH5UtPaDiRqoULERHh3oNv7Dgs0bTC0hO5Z2GdNokAMPT5w8ci8aMcRemcwbtdHjxChgtjbeA38GBdA==
-
"@react-native-community/eslint-config@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@react-native-community/eslint-config/-/eslint-config-3.1.0.tgz#80f9471bae00d0676b98436bbb3a596eca2d69ab"