diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4c2c1723..428edca7 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -152,8 +152,8 @@ android {
applicationId "co.psychedelic.plug"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 24
- versionName "0.3.0"
+ versionCode 27
+ versionName "0.4.0"
resValue "string", "build_config_package", "co.psychedelic.plug"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 35257cc3..a0e634b1 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -5,7 +5,7 @@
-
+
0.8.4)
- RNFileViewer (2.1.5):
- React-Core
- - RNFlashList (1.2.2):
+ - RNFlashList (1.4.0):
- React-Core
- RNGestureHandler (2.7.1):
- React-Core
@@ -447,7 +447,7 @@ PODS:
- React-Core
- RNReactNativeHapticFeedback (1.14.0):
- React-Core
- - RNReanimated (2.10.0):
+ - RNReanimated (2.12.0):
- DoubleConversion
- FBLazyVector
- FBReactNativeSpec
@@ -480,7 +480,7 @@ PODS:
- RNSentry (4.5.0):
- React-Core
- Sentry (= 7.25.1)
- - RNSVG (13.4.0):
+ - RNSVG (13.5.0):
- React-Core
- SDWebImage (5.11.1):
- SDWebImage/Core (= 5.11.1)
@@ -572,7 +572,7 @@ DEPENDENCIES:
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- RNBootSplash (from `../node_modules/react-native-bootsplash`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- - "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)"
+ - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)"
- "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)"
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
- RNFastImage (from `../node_modules/react-native-fast-image`)
@@ -713,7 +713,7 @@ EXTERNAL SOURCES:
RNCAsyncStorage:
:path: "../node_modules/@react-native-async-storage/async-storage"
RNCClipboard:
- :path: "../node_modules/@react-native-community/clipboard"
+ :path: "../node_modules/@react-native-clipboard/clipboard"
RNCMaskedView:
:path: "../node_modules/@react-native-masked-view/masked-view"
RNDeviceInfo:
@@ -816,19 +816,19 @@ SPEC CHECKSUMS:
ReactCommon: 01064177e66d652192c661de899b1076da962fd9
RNBootSplash: 5f346163977573d6b2aeba1b25df9d2245c0d73c
RNCAsyncStorage: 0c357f3156fcb16c8589ede67cc036330b6698ca
- RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
+ RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
RNCMaskedView: cb9670ea9239998340eaab21df13fa12a1f9de15
- RNDeviceInfo: 8b83e99e290deaf1bab71673cc636776ef647b6f
+ RNDeviceInfo: 4701f0bf2a06b34654745053db0ce4cb0c53ada7
RNFastImage: 3207b9eb17c2425d574ca40db35185db6e324f4e
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
- RNFlashList: 13d14d9502661134ad3ba892f81d76bdcbd79755
+ RNFlashList: 399bf6a0db68f594ad2c86aaff3ea39564f39f8a
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
RNLocalize: a64514b46a01375fdfae9349036b4dc7130333b5
RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c
- RNReanimated: 60e291d42c77752a0f6d6f358387bdf225a87c6e
+ RNReanimated: 2a91e85fcd343f8af3c58d3425b99fdd285590a5
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
RNSentry: a034d0e81e3d3c04b770dd2df953fe634d372d22
- RNSVG: 07dbd870b0dcdecc99b3a202fa37c8ca163caec2
+ RNSVG: 38ca962c970dbce1ca38991a5aebf26d163f9efb
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: f93010f3f6c031e2f8fb3081ca4ee6966c539815
Sentry: dd29c18c32b0af9269949f079cf631d581ca76ca
diff --git a/package.json b/package.json
index bfd6df41..a4d5b932 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
"android:bundle.prod": "yarn run ic:build.android && yarn run patch:android && cd android && ./gradlew clean && ./gradlew bundleProductionRelease",
"start": "react-native start",
"test": "jest",
- "lint": "eslint .",
+ "lint": "eslint . --max-warnings=0",
"nodeify": "rn-nodeify --hack --install crypto,buffer,react-native-randombytes,process,stream",
"lint-fix": "eslint . --fix",
"removeDeps": "rm -rf yarn.lock node_modules && cd ios && rm -rf Pods Podfile.lock && cd ..",
@@ -33,20 +33,20 @@
"@dfinity/candid": "0.9.3",
"@dfinity/principal": "^0.9.3",
"@hookform/error-message": "^2.0.0",
- "@psychedelic/dab-js": "1.4.12",
- "@psychedelic/plug-controller": "0.24.5",
+ "@psychedelic/dab-js": "^1.6.0-alpha.2",
+ "@psychedelic/plug-controller": "0.25.3",
"@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",
- "@react-navigation/material-top-tabs": "^6.2.4",
- "@react-navigation/native": "^6.0.8",
- "@react-navigation/stack": "^6.2.0",
+ "@react-navigation/material-top-tabs": "^6.3.0",
+ "@react-navigation/native": "^6.0.13",
+ "@react-navigation/stack": "^6.3.4",
"@reduxjs/toolkit": "^1.8.6",
"@sentry/react-native": "^4.5.0",
- "@shopify/flash-list": "^1.2.2",
+ "@shopify/flash-list": "^1.4.0",
"@walletconnect/client": "^1.7.5",
"assert-browserify": "^2.0.0",
"axios": "0.24.0",
@@ -61,6 +61,7 @@
"i18next": "^21.8.0",
"js-sha256": "^0.9.0",
"json-bigint": "^1.0.0",
+ "mime-types": "^2.1.35",
"patch-package": "^6.4.7",
"process": "^0.11.10",
"punycode": "^2.1.1",
@@ -74,7 +75,7 @@
"react-native-config": "^1.4.6",
"react-native-crypto": "^2.2.0",
"react-native-crypto-js": "^1.0.0",
- "react-native-device-info": "^10.2.0",
+ "react-native-device-info": "^10.3.0",
"react-native-document-picker": "^8.1.1",
"react-native-fast-image": "^8.6.1",
"react-native-fetch-api": "^2.0.0",
@@ -90,11 +91,11 @@
"react-native-portalize": "^1.0.7",
"react-native-process-shim": "^1.1.1",
"react-native-randombytes": "^3.6.1",
- "react-native-reanimated": "^2.10.0",
+ "react-native-reanimated": "^2.12.0",
"react-native-safe-area-context": "^4.4.1",
"react-native-screens": "^3.14.1",
"react-native-sensitive-info": "^6.0.0-alpha.9",
- "react-native-svg": "^13.4.0",
+ "react-native-svg": "^13.5.0",
"react-native-tab-view": "^3.1.1",
"react-native-toast-notifications": "^3.3.1",
"react-native-url-polyfill": "^1.3.0",
@@ -118,7 +119,7 @@
"devDependencies": {
"@babel/core": "^7.18.5",
"@babel/runtime": "7.18.3",
- "@react-native-community/eslint-config": "^2.0.0",
+ "@react-native-community/eslint-config": "^3.1.0",
"@rnx-kit/dep-check": "^1.13.1",
"@types/jest": "^27.5.1",
"@types/react-native": "0.66.3",
@@ -128,9 +129,11 @@
"babel-plugin-module-resolver": "^4.1.0",
"babel-plugin-transform-exponentiation-operator": "^6.24.1",
"eslint": "^7.32.0",
+ "eslint-plugin-ft-flow": "^2.0.1",
"eslint-plugin-simple-import-sort": "^7.0.0",
"jest": "^26.6.3",
"metro-react-native-babel-preset": "^0.72.1",
+ "prettier": "^2.7.1",
"react-native-svg-transformer": "^1.0.0",
"react-test-renderer": "18.1.0",
"typescript": "4.6.4"
diff --git a/patches/mime-types+2.1.35.patch b/patches/mime-types+2.1.35.patch
new file mode 100644
index 00000000..6ecd4b1c
--- /dev/null
+++ b/patches/mime-types+2.1.35.patch
@@ -0,0 +1,18 @@
+diff --git a/node_modules/mime-types/index.js b/node_modules/mime-types/index.js
+index b9f34d5..68ccbe9 100644
+--- a/node_modules/mime-types/index.js
++++ b/node_modules/mime-types/index.js
+@@ -13,7 +13,12 @@
+ */
+
+ var db = require('mime-db')
+-var extname = require('path').extname
++
++const extname = (path) => {
++ if (!path || path.indexOf('.') === -1) { return '' }
++ path = '.' + path.split('.').pop().toLowerCase()
++ return /.*(\..*)/g.exec(path)[1] || ''
++}
+
+ /**
+ * Module variables.
diff --git a/patches/react-native+0.70.3.patch b/patches/react-native+0.70.3.patch
new file mode 100644
index 00000000..72c39604
--- /dev/null
+++ b/patches/react-native+0.70.3.patch
@@ -0,0 +1,15 @@
+diff --git a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
+index 6a343d8..474b2ec 100644
+--- a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
++++ b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
+@@ -111,6 +111,10 @@ class KeyboardAvoidingView extends React.Component {
+ this._initialFrameHeight = this._frame.height;
+ }
+
++ if (this.props.onLayout) {
++ event.persist();
++ }
++
+ if (wasFrameNull) {
+ await this._updateBottomIfNecessary();
+ }
diff --git a/src/components/buttons/Button/index.tsx b/src/components/buttons/Button/index.tsx
index bf1cacbe..9762c9d5 100644
--- a/src/components/buttons/Button/index.tsx
+++ b/src/components/buttons/Button/index.tsx
@@ -41,7 +41,6 @@ const Button = ({
disableAnimation = false,
loading = false,
iconProps,
- ...props
}: Props) => {
return (
-
+
{loading ? (
) : (
diff --git a/src/components/buttons/RainbowButton/index.tsx b/src/components/buttons/RainbowButton/index.tsx
index b88db528..030a57ed 100644
--- a/src/components/buttons/RainbowButton/index.tsx
+++ b/src/components/buttons/RainbowButton/index.tsx
@@ -26,7 +26,6 @@ const RainbowButton = ({
disabled,
onLongPress,
loading = false,
- ...props
}: Props) => {
const disabledOrLoading = disabled || loading;
return (
@@ -36,8 +35,7 @@ const RainbowButton = ({
disabled={disabledOrLoading}>
+ {...(disabledOrLoading ? DisabledRainbow : Rainbow)}>
diff --git a/src/components/buttons/ScrollableButton/hooks/useScrollHandler.ts b/src/components/buttons/ScrollableButton/hooks/useScrollHandler.ts
new file mode 100644
index 00000000..cd22c97a
--- /dev/null
+++ b/src/components/buttons/ScrollableButton/hooks/useScrollHandler.ts
@@ -0,0 +1,19 @@
+import { useState } from 'react';
+import { NativeScrollEvent, NativeSyntheticEvent } from 'react-native';
+
+function useScrollHanlder() {
+ const [scrollPosition, setScrollPosition] = useState(0);
+
+ const handleOnScroll = (
+ scrollEvent: NativeSyntheticEvent
+ ) => {
+ setScrollPosition(scrollEvent?.nativeEvent?.contentOffset.y);
+ };
+
+ return {
+ scrollPosition,
+ handleOnScroll,
+ };
+}
+
+export default useScrollHanlder;
diff --git a/src/components/buttons/ScrollableButton/index.tsx b/src/components/buttons/ScrollableButton/index.tsx
new file mode 100644
index 00000000..32661282
--- /dev/null
+++ b/src/components/buttons/ScrollableButton/index.tsx
@@ -0,0 +1,92 @@
+import React, { useEffect, useState } from 'react';
+import { StyleProp, TextStyle, ViewStyle } from 'react-native';
+import Animated, {
+ Easing,
+ useAnimatedStyle,
+ useSharedValue,
+ withTiming,
+} from 'react-native-reanimated';
+
+import { Touchable } from '@/components/common';
+import AddGradient from '@/icons/svg/AddGradient.svg';
+
+import styles from './styles';
+
+interface Props {
+ onPress: () => void;
+ text: string;
+ textWidth: number;
+ buttonStyle?: StyleProp;
+ imageStyle?: StyleProp;
+ textStyle?: StyleProp;
+ scrollPosition: number;
+}
+
+function ScrollableButton({
+ onPress,
+ text,
+ textWidth,
+ textStyle,
+ buttonStyle,
+ imageStyle,
+ scrollPosition,
+}: Props) {
+ const [showFullButton, setShowFullButton] = useState(true);
+ const [currentScrollPosition, setCurrentScrollPosition] = useState(0);
+
+ const animatedWidth = useSharedValue(textWidth);
+
+ const animatedStyle = useAnimatedStyle(() => {
+ return {
+ width: withTiming(animatedWidth.value, {
+ duration: 200,
+ easing: Easing.bezier(0.25, 0.1, 0.25, 1),
+ }),
+ opacity: withTiming(animatedWidth.value > 0 ? 1 : 0, {
+ duration: 450,
+ }),
+ };
+ });
+
+ useEffect(() => {
+ if (scrollPosition < 0) {
+ // to capture ios bounce effect
+ setShowFullButton(true);
+ animatedWidth.value = textWidth;
+ setCurrentScrollPosition(0);
+ } else if (Math.abs(scrollPosition - currentScrollPosition) > 100) {
+ if (currentScrollPosition < scrollPosition) {
+ // Scroll down.
+ setShowFullButton(false);
+ animatedWidth.value = 0;
+ } else if (
+ scrollPosition < currentScrollPosition ||
+ scrollPosition === 0
+ ) {
+ // Scroll up or start position.
+ setShowFullButton(true);
+ animatedWidth.value = textWidth;
+ }
+ setCurrentScrollPosition(scrollPosition);
+ }
+ }, [scrollPosition]);
+
+ return (
+
+
+
+ {text}
+
+
+ );
+}
+
+export default ScrollableButton;
diff --git a/src/components/buttons/ScrollableButton/styles.ts b/src/components/buttons/ScrollableButton/styles.ts
new file mode 100644
index 00000000..922dc226
--- /dev/null
+++ b/src/components/buttons/ScrollableButton/styles.ts
@@ -0,0 +1,30 @@
+import { StyleSheet } from 'react-native';
+
+import { Colors, FontStyles } from '@/constants/theme';
+import { fontMaker } from '@/utils/fonts';
+
+export default StyleSheet.create({
+ button: {
+ minWidth: 48,
+ height: 48,
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: Colors.ActionBlue,
+ borderRadius: 100,
+ paddingHorizontal: 16,
+ shadowColor: Colors.White.Pure,
+ shadowOffset: { width: 2, height: 2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 12,
+ elevation: 4,
+ },
+ disabled: {
+ opacity: 0.2,
+ },
+ text: {
+ ...fontMaker({ ...FontStyles.Body2, color: Colors.White.Pure }),
+ },
+ marginText: {
+ marginLeft: 8,
+ },
+});
diff --git a/src/components/common/AccountInfo/index.js b/src/components/common/AccountInfo/index.tsx
similarity index 79%
rename from src/components/common/AccountInfo/index.js
rename to src/components/common/AccountInfo/index.tsx
index a3986017..ab851c2c 100644
--- a/src/components/common/AccountInfo/index.js
+++ b/src/components/common/AccountInfo/index.tsx
@@ -1,14 +1,12 @@
-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';
-import CopiedToast from '@/commonComponents/CopiedToast';
-import Touchable from '@/commonComponents/Touchable';
+import { CopiedToast, Text, Touchable } from '@/components/common';
import { FontStyles } from '@/constants/theme';
import { useAppSelector } from '@/redux/hooks';
import shortAddress from '@/utils/shortAddress';
-import Text from '../Text';
import styles from './styles';
const AccountInfo = () => {
@@ -24,7 +22,7 @@ const AccountInfo = () => {
}, []);
const copyToClipboard = async () => {
- Clipboard.setString(principal);
+ Clipboard.setString(principal!);
setVisibility(true);
};
@@ -32,7 +30,7 @@ const AccountInfo = () => {
<>
- {reverseResolvedName || name}
+ {reverseResolvedName || name}
{shortAddress(principal)}
diff --git a/src/components/common/AccountInfo/styles.js b/src/components/common/AccountInfo/styles.ts
similarity index 100%
rename from src/components/common/AccountInfo/styles.js
rename to src/components/common/AccountInfo/styles.ts
diff --git a/src/screens/tabs/Profile/modals/ExportPem/components/AccountShowcase/index.tsx b/src/components/common/AccountShowcase/index.tsx
similarity index 62%
rename from src/screens/tabs/Profile/modals/ExportPem/components/AccountShowcase/index.tsx
rename to src/components/common/AccountShowcase/index.tsx
index 68aa21aa..17409158 100644
--- a/src/screens/tabs/Profile/modals/ExportPem/components/AccountShowcase/index.tsx
+++ b/src/components/common/AccountShowcase/index.tsx
@@ -1,7 +1,7 @@
import React from 'react';
-import { StyleProp, View, ViewStyle } from 'react-native';
+import { StyleProp, TextStyle, View, ViewStyle } from 'react-native';
-import { CustomCheckbox, Text, Touchable, UserIcon } from '@/components/common';
+import { Text, Touchable, UserIcon } from '@/components/common';
import { FontStyles } from '@/constants/theme';
import styles from './styles';
@@ -11,6 +11,9 @@ interface Props {
subtitle?: string;
icon?: string;
selected: boolean;
+ right: React.ReactNode;
+ titleStyle?: StyleProp;
+ titleRight?: React.ReactNode;
style?: StyleProp;
onPress?: () => void;
onLongPress?: () => void;
@@ -22,6 +25,9 @@ function AccountShowcase({
subtitle,
style,
selected,
+ right,
+ titleStyle,
+ titleRight,
onPress,
onLongPress,
}: Props) {
@@ -31,12 +37,13 @@ function AccountShowcase({
- {title}
+
+ {title}
+ {titleRight}
+
{subtitle}
-
-
-
+ {right}
diff --git a/src/screens/tabs/Profile/modals/ExportPem/components/AccountShowcase/styles.ts b/src/components/common/AccountShowcase/styles.ts
similarity index 86%
rename from src/screens/tabs/Profile/modals/ExportPem/components/AccountShowcase/styles.ts
rename to src/components/common/AccountShowcase/styles.ts
index 656fc150..2053ef27 100644
--- a/src/screens/tabs/Profile/modals/ExportPem/components/AccountShowcase/styles.ts
+++ b/src/components/common/AccountShowcase/styles.ts
@@ -23,4 +23,8 @@ export default StyleSheet.create({
alignItems: 'flex-end',
marginRight: 8,
},
+ titleContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
});
diff --git a/src/components/common/Badge/index.js b/src/components/common/Badge/index.tsx
similarity index 76%
rename from src/components/common/Badge/index.js
rename to src/components/common/Badge/index.tsx
index 63ea9cc7..c02d85a7 100644
--- a/src/components/common/Badge/index.js
+++ b/src/components/common/Badge/index.tsx
@@ -1,10 +1,17 @@
import React from 'react';
import { Image, View } from 'react-native';
-import Text from '../Text';
+import { Text } from '@/components/common';
+
import styles from './styles';
-const Badge = ({ name, value, icon }) => (
+interface Props {
+ name?: string;
+ value: string;
+ icon?: string;
+}
+
+const Badge = ({ name, value, icon }: Props) => (
{name && {name}}
diff --git a/src/components/common/Badge/styles.js b/src/components/common/Badge/styles.ts
similarity index 100%
rename from src/components/common/Badge/styles.js
rename to src/components/common/Badge/styles.ts
diff --git a/src/components/common/CommonItem/index.tsx b/src/components/common/CommonItem/index.tsx
index 2a76e4a3..15bc82c1 100644
--- a/src/components/common/CommonItem/index.tsx
+++ b/src/components/common/CommonItem/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React from 'react';
import { StyleProp, TextStyle, View, ViewStyle } from 'react-native';
import Touchable from '@/commonComponents/Touchable';
@@ -59,8 +59,7 @@ function CommonItem({
disabled,
showActions = true,
}: Props) {
- const [imageType, setImageType] = useState('');
- useGetType(imageUri, setImageType);
+ const imageType = useGetType(imageUri);
const formattedId = shortAddress(id, longId ? longIdConfig : undefined);
diff --git a/src/components/common/CopiedToast/index.js b/src/components/common/CopiedToast/index.tsx
similarity index 81%
rename from src/components/common/CopiedToast/index.js
rename to src/components/common/CopiedToast/index.tsx
index b738b184..6894ec12 100644
--- a/src/components/common/CopiedToast/index.js
+++ b/src/components/common/CopiedToast/index.tsx
@@ -1,21 +1,28 @@
import { t } from 'i18next';
import React, { useEffect, useRef } from 'react';
-import { Animated, View } from 'react-native';
+import { Animated, StyleProp, View, ViewStyle } from 'react-native';
+import { Text } from '@/components/common';
import { FontStyles } from '@/constants/theme';
-import Text from '../Text';
import styles from './styles';
const TOAST_DURATION = 2500;
const TOAST_ANIMATION_SPEED = 200;
+interface Props {
+ visibility: boolean;
+ setVisibility: (value: boolean) => void;
+ customStyle?: StyleProp;
+ customPointerStyle?: StyleProp;
+}
+
function CopiedToast({
visibility,
setVisibility,
customStyle,
customPointerStyle,
-}) {
+}: Props) {
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
diff --git a/src/components/common/CopiedToast/styles.js b/src/components/common/CopiedToast/styles.ts
similarity index 100%
rename from src/components/common/CopiedToast/styles.js
rename to src/components/common/CopiedToast/styles.ts
diff --git a/src/components/common/Copy/index.js b/src/components/common/Copy/index.tsx
similarity index 74%
rename from src/components/common/Copy/index.js
rename to src/components/common/Copy/index.tsx
index 5511244b..6db428cf 100644
--- a/src/components/common/Copy/index.js
+++ b/src/components/common/Copy/index.tsx
@@ -1,17 +1,20 @@
-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 { View } from 'react-native';
+import { StyleProp, View, ViewStyle } from 'react-native';
-import CopiedToast from '@/commonComponents/CopiedToast';
-import Touchable from '@/commonComponents/Touchable';
+import { CopiedToast, Text, Touchable } from '@/components/common';
import { Colors } from '@/constants/theme';
import Icon from '@/icons';
-import Text from '../Text';
import styles from './styles';
-const Copy = ({ text, customStyle }) => {
+interface Props {
+ text: string;
+ customStyle?: StyleProp;
+}
+
+function Copy({ text, customStyle }: Props) {
const [visibility, setVisibility] = useState(false);
useEffect(() => {
@@ -37,6 +40,6 @@ const Copy = ({ text, customStyle }) => {
);
-};
+}
export default Copy;
diff --git a/src/components/common/Copy/styles.js b/src/components/common/Copy/styles.ts
similarity index 100%
rename from src/components/common/Copy/styles.js
rename to src/components/common/Copy/styles.ts
diff --git a/src/components/common/EmptyState/index.js b/src/components/common/EmptyState/index.js
deleted file mode 100644
index 02d12d8b..00000000
--- a/src/components/common/EmptyState/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import { View } from 'react-native';
-
-import Text from '../Text';
-import styles from './styles';
-
-const EmptyState = ({ title, text, style, onTextPress }) => (
-
- 🤔
-
- {title}
-
-
- {text}
-
-
-);
-
-export default EmptyState;
diff --git a/src/components/common/EmptyState/index.tsx b/src/components/common/EmptyState/index.tsx
new file mode 100644
index 00000000..27fb2503
--- /dev/null
+++ b/src/components/common/EmptyState/index.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import { StyleProp, View, ViewStyle } from 'react-native';
+
+import RainbowButton from '@/components/buttons/RainbowButton';
+
+import Text from '../Text';
+import styles from './styles';
+
+interface Props {
+ title: string;
+ text: string;
+ emoji?: string;
+ style?: StyleProp;
+ buttonTitle?: string;
+ buttonStyle?: StyleProp;
+ onTextPress?: () => void;
+ onButtonPress?: () => void;
+}
+
+function EmptyState({
+ title,
+ text,
+ style,
+ emoji = '🤔',
+ buttonTitle,
+ buttonStyle,
+ onTextPress,
+ onButtonPress,
+}: Props) {
+ return (
+
+ {emoji}
+
+ {title}
+
+
+ {text}
+
+ {onButtonPress && buttonTitle && (
+
+ )}
+
+ );
+}
+
+export default EmptyState;
diff --git a/src/components/common/EmptyState/styles.js b/src/components/common/EmptyState/styles.ts
similarity index 66%
rename from src/components/common/EmptyState/styles.js
rename to src/components/common/EmptyState/styles.ts
index 149e4b78..1beeda05 100644
--- a/src/components/common/EmptyState/styles.js
+++ b/src/components/common/EmptyState/styles.ts
@@ -1,6 +1,7 @@
import { StyleSheet } from 'react-native';
import { Colors } from '@/constants/theme';
+import { fontMaker } from '@/utils/fonts';
export default StyleSheet.create({
container: {
@@ -9,17 +10,17 @@ export default StyleSheet.create({
justifyContent: 'center',
},
title: {
- color: Colors.White.Primary,
+ ...fontMaker({ color: Colors.White.Primary }),
marginTop: 11,
marginBottom: 8,
},
text: {
- color: Colors.White.Secondary,
+ ...fontMaker({ color: Colors.White.Secondary }),
textAlign: 'center',
paddingHorizontal: 40,
},
link: {
- color: Colors.ActionBlue,
+ ...fontMaker({ color: Colors.ActionBlue }),
},
emoji: {
fontSize: 38,
diff --git a/src/components/common/ErrorBoundary/index.js b/src/components/common/ErrorBoundary/index.js
deleted file mode 100644
index 6e07361a..00000000
--- a/src/components/common/ErrorBoundary/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-
-import ErrorBoundaryScreen from '@/screens/error/ErrorBoundaryScreen';
-
-class ErrorBoundary extends React.Component {
- state = { hasError: null };
-
- static getDerivedStateFromError(error) {
- return { hasError: error };
- }
-
- componentDidCatch(error, info) {
- this.logErrorToServices(error.toString(), info.componentStack);
- }
- // A fake logging service 😬
- logErrorToServices = console.log;
- render() {
- if (this.state.hasError) {
- return ;
- }
- return this.props.children;
- }
-}
-
-export default ErrorBoundary;
diff --git a/src/components/common/ErrorBoundary/index.tsx b/src/components/common/ErrorBoundary/index.tsx
new file mode 100644
index 00000000..9b340977
--- /dev/null
+++ b/src/components/common/ErrorBoundary/index.tsx
@@ -0,0 +1,34 @@
+import React, { Component, ErrorInfo, ReactNode } from 'react';
+
+import ErrorBoundaryScreen from '@/screens/error/ErrorBoundaryScreen';
+
+interface Props {
+ children?: ReactNode;
+}
+
+interface State {
+ hasError: boolean;
+}
+
+class ErrorBoundary extends Component {
+ public state: State = { hasError: false };
+
+ public static getDerivedStateFromError(_: Error): State {
+ return { hasError: true };
+ }
+
+ public componentDidCatch(error: Error, info: ErrorInfo) {
+ this.logErrorToServices(error.toString(), info.componentStack);
+ }
+ // A fake logging service 😬
+ logErrorToServices = console.log;
+
+ public render() {
+ if (this.state.hasError) {
+ return ;
+ }
+ return this.props.children;
+ }
+}
+
+export default ErrorBoundary;
diff --git a/src/components/common/ErrorState/constants.js b/src/components/common/ErrorState/constants.ts
similarity index 63%
rename from src/components/common/ErrorState/constants.js
rename to src/components/common/ErrorState/constants.ts
index c7f2da8e..30e1a865 100644
--- a/src/components/common/ErrorState/constants.js
+++ b/src/components/common/ErrorState/constants.ts
@@ -2,18 +2,25 @@ import { t } from 'i18next';
import { ERROR_TYPES } from '@/constants/general';
-export const getErrorStateData = type =>
+export interface ErrorStateData {
+ emoji: string;
+ title: string;
+ description: string;
+ buttonTitle: string;
+}
+
+export const getErrorStateData = (type: ERROR_TYPES) =>
({
[ERROR_TYPES.FETCH_ERROR]: t(`errors.${ERROR_TYPES.FETCH_ERROR}`, {
returnObjects: true,
- }),
+ }) as ErrorStateData,
[ERROR_TYPES.CONNECTION_ERROR]: t(
`errors.${ERROR_TYPES.CONNECTION_ERROR}`,
{
returnObjects: true,
}
- ),
+ ) as ErrorStateData,
[ERROR_TYPES.ERROR_BOUNDARY]: t(`errors.${ERROR_TYPES.ERROR_BOUNDARY}`, {
returnObjects: true,
- }),
+ }) as ErrorStateData,
}[type] || null);
diff --git a/src/components/common/ErrorState/index.js b/src/components/common/ErrorState/index.tsx
similarity index 51%
rename from src/components/common/ErrorState/index.js
rename to src/components/common/ErrorState/index.tsx
index 6fb49fbc..ffd6b8dd 100644
--- a/src/components/common/ErrorState/index.js
+++ b/src/components/common/ErrorState/index.tsx
@@ -1,21 +1,37 @@
import React from 'react';
-import { View } from 'react-native';
+import { StyleProp, View, ViewStyle } from 'react-native';
import Button from '@/buttons/Button';
+import { ERROR_TYPES } from '@/constants/general';
import { FontStyles } from '@/constants/theme';
import Text from '../Text';
-import { getErrorStateData } from './constants';
+import { ErrorStateData, getErrorStateData } from './constants';
import styles from './styles';
-function ErrorState({ errorType, onPress, loading, style, buttonStyle }) {
- const { title, emoji, description, buttonTitle, buttonImage } =
- getErrorStateData(errorType);
+interface Props {
+ errorType: ERROR_TYPES;
+ onPress?: () => void;
+ loading?: boolean;
+ style?: StyleProp;
+ buttonStyle?: StyleProp;
+}
+
+function ErrorState({
+ errorType,
+ onPress,
+ loading,
+ style,
+ buttonStyle,
+}: Props) {
+ const { title, emoji, description, buttonTitle } = getErrorStateData(
+ errorType
+ ) as ErrorStateData;
return (
{emoji}
- {title}
+ {title}
{description}
@@ -24,7 +40,6 @@ function ErrorState({ errorType, onPress, loading, style, buttonStyle }) {
onPress={onPress}
loading={loading}
text={buttonTitle}
- iconName={buttonImage}
buttonStyle={[styles.button, buttonStyle]}
/>
)}
diff --git a/src/components/common/ErrorState/styles.js b/src/components/common/ErrorState/styles.ts
similarity index 100%
rename from src/components/common/ErrorState/styles.js
rename to src/components/common/ErrorState/styles.ts
diff --git a/src/components/common/KeyboardScrollView/index.js b/src/components/common/KeyboardScrollView/index.tsx
similarity index 74%
rename from src/components/common/KeyboardScrollView/index.js
rename to src/components/common/KeyboardScrollView/index.tsx
index af1a1392..672b95da 100644
--- a/src/components/common/KeyboardScrollView/index.js
+++ b/src/components/common/KeyboardScrollView/index.tsx
@@ -1,14 +1,32 @@
import { HeaderHeightContext } from '@react-navigation/elements';
import React from 'react';
+import { ScrollViewProps } from 'react-native';
import {
KeyboardAvoidingView,
Platform,
SafeAreaView,
ScrollView,
+ StyleProp,
+ ViewStyle,
} from 'react-native';
import styles from './styles';
+interface Props {
+ children: React.ReactNode;
+ keyboardShouldPersistTaps?:
+ | boolean
+ | 'always'
+ | 'never'
+ | 'handled'
+ | undefined;
+ keyboardStyle?: StyleProp;
+ safeAreaStyle?: StyleProp;
+ scrollviewRef?: React.RefObject;
+ scrollViewProps?: ScrollViewProps;
+ contentStyle?: StyleProp;
+}
+
function KeyboardScrollView({
children,
keyboardShouldPersistTaps,
@@ -17,7 +35,7 @@ function KeyboardScrollView({
scrollviewRef,
scrollViewProps,
contentStyle,
-}) {
+}: Props) {
return (
{headerHeight => (
diff --git a/src/components/common/KeyboardScrollView/styles.js b/src/components/common/KeyboardScrollView/styles.ts
similarity index 100%
rename from src/components/common/KeyboardScrollView/styles.js
rename to src/components/common/KeyboardScrollView/styles.ts
diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx
index 9a3923ac..678a1f85 100644
--- a/src/components/common/Modal/index.tsx
+++ b/src/components/common/Modal/index.tsx
@@ -1,6 +1,7 @@
import React, { useEffect } from 'react';
import { StyleProp, ViewStyle } from 'react-native';
import { Modalize } from 'react-native-modalize';
+import { IProps as ModalizeProps } from 'react-native-modalize/lib/options';
import { Portal } from 'react-native-portalize';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -11,7 +12,7 @@ import styles from './styles';
export const modalOffset = withNotch ? undefined : isIos ? 10 : 35;
-interface Props {
+interface Props extends ModalizeProps {
children: React.ReactNode;
modalRef: React.RefObject;
onClose?: () => void;
@@ -39,10 +40,10 @@ function Modal({
FooterComponent,
FloatingComponent,
disableScrollIfPossible,
+ onBackButtonPress,
...props
}: Props) {
const closeAllModals = useAppSelector(state => state.alert.closeAllModals);
- const handleOnClose = () => onClose?.();
useEffect(() => {
if (closeAllModals) {
@@ -55,6 +56,7 @@ function Modal({
void;
url: string;
style: StyleProp;
- isSendView?: boolean;
- isDetailView?: boolean;
type: string;
}
-function HTMLDisplayer({
- loading,
- onLoad,
- url,
- style,
- isSendView,
- type,
- isDetailView,
-}: Props) {
- const webViewRef = useRef(null);
+const Spinner = ({ style }: { style?: StyleProp }) => (
+
+
+
+);
- const Spinner = () => (
-
-
-
- );
+function HTMLDisplayer({ loading, onLoad, url, style, type }: Props) {
+ const webViewRef = useRef(null);
return (
-
+
}
+ style={[sharedStyles.webView, style]}
/>
- {loading && (
-
- )}
+ {loading && }
);
}
diff --git a/src/components/common/NftDisplayer/components/ICNSDisplayer/index.tsx b/src/components/common/NftDisplayer/components/ICNSDisplayer/index.tsx
index 05f00ebe..3cf59487 100644
--- a/src/components/common/NftDisplayer/components/ICNSDisplayer/index.tsx
+++ b/src/components/common/NftDisplayer/components/ICNSDisplayer/index.tsx
@@ -15,7 +15,7 @@ function ICNSDisplayer({ ICNSName, size = 'small' }: Props) {
const isBigSize = size === 'big';
return (
-
+
;
url: string;
- isSendView?: boolean;
- isDetailView?: boolean;
- type?: string; //TODO: Improve this
+ type?: string;
}
-function ImageDisplayer({ style, type, url, isSendView, isDetailView }: Props) {
- const innerStyle = isSendView
- ? { width: 54, height: 54 }
- : { width: '100%', height: '100%' };
-
+function ImageDisplayer({ style, type, url }: Props) {
return (
+
}>
{type?.includes('svg') || type?.includes('html') ? (
-
+
) : (
-
+
)}
);
diff --git a/src/components/common/NftDisplayer/components/VideoDisplayer/index.tsx b/src/components/common/NftDisplayer/components/VideoDisplayer/index.tsx
index b0f6453f..71154d22 100644
--- a/src/components/common/NftDisplayer/components/VideoDisplayer/index.tsx
+++ b/src/components/common/NftDisplayer/components/VideoDisplayer/index.tsx
@@ -1,61 +1,48 @@
-import React from 'react';
-import { ActivityIndicator, StyleProp, View, ViewStyle } from 'react-native';
+import React, { useState } from 'react';
+import { ActivityIndicator, StyleProp, ViewStyle } from 'react-native';
import Video from 'react-native-video';
import useFileDownload from '@/hooks/useFileDownload';
+import { getExtension } from '@/utils/fileTypes';
import sharedStyles from '../../styles';
interface Props {
- onLoad?: () => void;
style: StyleProp;
url: string;
- loading?: boolean;
- isDetailView?: boolean;
- isSendView?: boolean;
+ type: string;
+ paused?: boolean;
+ filename?: string;
}
-const VideoDisplayer = ({
- onLoad,
- style,
- url,
- loading,
- isDetailView,
- isSendView,
-}: Props) => {
- const newUrl = useFileDownload({ url, format: 'mp4' });
+const VideoDisplayer = ({ style, url, paused, type, filename }: Props) => {
+ const [loading, setLoading] = useState(true);
+ const format = getExtension(type) || 'mp4';
+ const newUrl = useFileDownload({ url, format, filename });
+
+ const hideSpinner = () => {
+ setLoading(false);
+ };
return (
-
+ <>
{loading && !newUrl ? (
-
+
) : (
)}
-
+ >
);
};
diff --git a/src/components/common/NftDisplayer/index.tsx b/src/components/common/NftDisplayer/index.tsx
index 99a30183..a3788345 100644
--- a/src/components/common/NftDisplayer/index.tsx
+++ b/src/components/common/NftDisplayer/index.tsx
@@ -1,66 +1,52 @@
-import React, { useState } from 'react';
-import { ActivityIndicator, StyleProp, View, ViewStyle } from 'react-native';
+import React from 'react';
+import { ActivityIndicator, StyleProp, ViewStyle } from 'react-native';
+
+import { deleteWhiteSpaces } from '@/utils/strings';
import ICNSDisplayer from './components/ICNSDisplayer';
import ImageDisplayer from './components/ImageDisplayer';
import VideoDisplayer from './components/VideoDisplayer';
-import styles from './styles';
interface Props {
url: string;
- style: StyleProp;
+ canisterId: string;
+ itemId: string | number;
+ style?: StyleProp;
type?: string;
isDetailView?: boolean;
- isSend?: boolean;
ICNSName?: string;
+ icnsSize?: 'big' | 'small';
+ paused?: boolean;
}
const NftDisplayer = ({
url,
style,
type,
- isDetailView,
- isSend,
+ icnsSize,
ICNSName,
+ canisterId,
+ itemId,
+ paused,
}: Props) => {
- const [loading, setLoading] = useState(true);
-
- const hideSpinner = () => {
- setLoading(false);
- };
-
+ const uniqueId = deleteWhiteSpaces(`${canisterId}${itemId}`);
return type ? (
-
- {type?.includes('video') ? (
+ <>
+ {type?.includes('video/') ? (
- ) : (
-
- )}
- {ICNSName && (
-
+ ) : (
+
)}
-
+ {ICNSName && }
+ >
) : (
-
+
);
};
diff --git a/src/components/common/NftDisplayer/styles.ts b/src/components/common/NftDisplayer/styles.ts
index ebbd16a0..9526c1c3 100644
--- a/src/components/common/NftDisplayer/styles.ts
+++ b/src/components/common/NftDisplayer/styles.ts
@@ -3,92 +3,39 @@ import { StyleSheet, ViewStyle } from 'react-native';
import { WINDOW_WIDTH } from '@/constants/platform';
import { Colors } from '@/constants/theme';
-const videoDetailSize = WINDOW_WIDTH - 40;
-const viewSendSize = 54;
-
const itemSize = WINDOW_WIDTH / 2 - 40;
-const commonStyle = {
- borderRadius: 20,
- alignSelf: 'center',
-} as ViewStyle;
-
-const commonDetailContainer = {
- height: videoDetailSize,
- width: videoDetailSize,
-};
-
-const commonSendContainer = {
- height: viewSendSize,
- width: viewSendSize,
-};
-
const commonCotainer = {
- ...commonStyle,
+ alignSelf: 'center',
width: itemSize,
height: itemSize,
-};
-
-const commonWebViewStyles = {
- backgroundColor: 'transparent',
- flex: 0,
-};
+} as ViewStyle;
export default StyleSheet.create({
- image: {
- ...commonStyle,
- },
- container: {
- ...commonCotainer,
- },
- containerDetail: {
- ...commonDetailContainer,
- },
- video: {
- ...commonCotainer,
- },
- videoDetail: {
- ...commonDetailContainer,
- },
- activityIndicator: {
- position: 'absolute',
- top: 0,
- bottom: 0,
- right: 0,
- left: 0,
- backgroundColor: Colors.Black.Pure,
- ...commonStyle,
- },
- sendActivityIndicator: {
- ...commonSendContainer,
- },
+ video: commonCotainer,
webView: {
- ...commonWebViewStyles,
...commonCotainer,
- },
- webViewSend: {
- ...commonWebViewStyles,
- height: '100%',
- width: '100%',
- },
- containerSend: {
- ...commonSendContainer,
- },
- videoSend: {
- ...commonStyle,
- ...commonSendContainer,
+ backgroundColor: 'transparent',
+ flex: 0,
},
webViewLoader: {
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
top: 0,
+ bottom: 0,
+ right: 0,
left: 0,
- backgroundColor: Colors.Black.Primary,
+ backgroundColor: Colors.Black.Pure,
height: '100%',
width: '100%',
},
- webViewLoaderDetail: {
+ imageMask: {
+ borderRadius: 20,
backgroundColor: Colors.Black.Pure,
},
+ fullsize: {
+ width: '100%',
+ height: '100%',
+ },
});
diff --git a/src/components/common/SearchBar/index.tsx b/src/components/common/SearchBar/index.tsx
index 5ea80500..a748068e 100644
--- a/src/components/common/SearchBar/index.tsx
+++ b/src/components/common/SearchBar/index.tsx
@@ -13,17 +13,25 @@ interface Props {
onChangeText: (text: string) => void;
placeholder: string;
style?: StyleProp;
+ containerStyle?: StyleProp;
onActionPress?: () => void;
}
-function SearchBar({ placeholder, style, onChangeText, onActionPress }: Props) {
+function SearchBar({
+ placeholder,
+ style,
+ onChangeText,
+ onActionPress,
+ containerStyle,
+}: Props) {
return (
}
/>
{onActionPress && (
diff --git a/src/components/common/SearchBar/styles.ts b/src/components/common/SearchBar/styles.ts
index 005f72d6..008fbeda 100644
--- a/src/components/common/SearchBar/styles.ts
+++ b/src/components/common/SearchBar/styles.ts
@@ -1,6 +1,8 @@
import { StyleSheet } from 'react-native';
+import { SEMIBOLD } from '@/constants/fonts';
import { Colors } from '@/constants/theme';
+import { fontMaker } from '@/utils/fonts';
export const searchColor = Colors.White.Secondary;
@@ -33,4 +35,9 @@ export default StyleSheet.create({
borderRadius: 16,
marginLeft: 8,
},
+ inputStyle: fontMaker({
+ size: 16,
+ weight: SEMIBOLD,
+ color: Colors.White.Pure,
+ }),
});
diff --git a/src/components/common/ListItem/index.js b/src/components/common/SeedPhrase/components/ListItem/index.tsx
similarity index 66%
rename from src/components/common/ListItem/index.js
rename to src/components/common/SeedPhrase/components/ListItem/index.tsx
index 506f5cf5..31e78936 100644
--- a/src/components/common/ListItem/index.js
+++ b/src/components/common/SeedPhrase/components/ListItem/index.tsx
@@ -1,10 +1,16 @@
import React from 'react';
import { View } from 'react-native';
-import Text from '../Text';
+import { Text } from '@/components/common';
+
import styles from './styles';
-function ListItem({ number, text }) {
+interface Props {
+ number: number;
+ text: string;
+}
+
+function ListItem({ number, text }: Props) {
return (
{number}
diff --git a/src/components/common/ListItem/styles.js b/src/components/common/SeedPhrase/components/ListItem/styles.ts
similarity index 100%
rename from src/components/common/ListItem/styles.js
rename to src/components/common/SeedPhrase/components/ListItem/styles.ts
diff --git a/src/components/common/SeedPhrase/index.js b/src/components/common/SeedPhrase/index.tsx
similarity index 74%
rename from src/components/common/SeedPhrase/index.js
rename to src/components/common/SeedPhrase/index.tsx
index 2e3508c6..ac49dfd4 100644
--- a/src/components/common/SeedPhrase/index.js
+++ b/src/components/common/SeedPhrase/index.tsx
@@ -1,17 +1,21 @@
import { BlurView } from '@react-native-community/blur';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { Image, View } from 'react-native';
+import { Image, Pressable, View } from 'react-native';
import KeyImg from '@/assets/icons/key.png';
-import ListItem from '@/commonComponents/ListItem';
-import Touchable from '@/commonComponents/Touchable';
+import { Text, Touchable } from '@/components/common';
import { TestIds } from '@/constants/testIds';
-import Text from '../Text';
+import ListItem from './components/ListItem';
import styles from './styles';
-const SeedPhrase = ({ mnemonic, onReveal = () => null }) => {
+interface Props {
+ mnemonic: string[];
+ onReveal?: () => void;
+}
+
+const SeedPhrase = ({ mnemonic, onReveal = () => {} }: Props) => {
const { t } = useTranslation();
const [reveal, setReveal] = useState(false);
const revealSeedPhrase = () => {
@@ -20,11 +24,11 @@ const SeedPhrase = ({ mnemonic, onReveal = () => null }) => {
};
return (
-
- {mnemonic.map((word, i) => (
+ {mnemonic.map((word: string, i: number) => (
@@ -33,7 +37,7 @@ const SeedPhrase = ({ mnemonic, onReveal = () => null }) => {
<>
@@ -43,7 +47,7 @@ const SeedPhrase = ({ mnemonic, onReveal = () => null }) => {
>
)}
-
+
);
};
diff --git a/src/components/common/SeedPhrase/styles.js b/src/components/common/SeedPhrase/styles.ts
similarity index 100%
rename from src/components/common/SeedPhrase/styles.js
rename to src/components/common/SeedPhrase/styles.ts
diff --git a/src/components/common/Toast/index.tsx b/src/components/common/Toast/index.tsx
index 4348e8ef..b058d07e 100644
--- a/src/components/common/Toast/index.tsx
+++ b/src/components/common/Toast/index.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { View } from 'react-native';
+import { Pressable, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useToast } from 'react-native-toast-notifications';
@@ -12,7 +12,6 @@ import InfoIcon from '@/icons/svg/InfoIcon.svg';
import SuccessIcon from '@/icons/svg/SuccessIcon.svg';
import Text from '../Text';
-import Touchable from '../Touchable';
import styles from './styles';
export enum ToastTypes {
@@ -23,7 +22,7 @@ export enum ToastTypes {
export interface ToastProps {
title: string;
- message: string;
+ message?: string;
type: 'success' | 'error' | 'info';
id: string;
}
@@ -61,13 +60,15 @@ function Toast({ title, message, type, id }: ToastProps) {
{title}
-
+
-
+
-
- {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/common/index.ts b/src/components/common/index.ts
index d1c46982..0cfd876a 100644
--- a/src/components/common/index.ts
+++ b/src/components/common/index.ts
@@ -13,9 +13,7 @@ export { default as ErrorState } from './ErrorState';
export { default as GradientText } from './GradientText';
export { default as Header } from './Header';
export { default as Image } from './Image';
-export { default as InfoWithActions } from './InfoWithActions';
export { default as KeyboardScrollView } from './KeyboardScrollView';
-export { default as ListItem } from './ListItem';
export { default as Modal } from './Modal';
export { default as NftDisplayer } from './NftDisplayer';
export { default as PasswordInput } from './PasswordInput';
@@ -28,3 +26,4 @@ export { default as Touchable } from './Touchable';
export { default as UserIcon } from './UserIcon';
export { default as CustomCheckbox } from './CustomCheckbox';
export { default as Toast } from './Toast';
+export { default as AccountShowcase } from './AccountShowcase';
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/components/icons/svg/AddGradient.svg b/src/components/icons/svg/AddGradient.svg
new file mode 100644
index 00000000..0ea68c87
--- /dev/null
+++ b/src/components/icons/svg/AddGradient.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/components/icons/svg/Search.svg b/src/components/icons/svg/Search.svg
index ae4687eb..c6955e5e 100644
--- a/src/components/icons/svg/Search.svg
+++ b/src/components/icons/svg/Search.svg
@@ -1,3 +1,3 @@
-} />
@@ -134,7 +145,13 @@ const ReviewSend = ({
{`#${nft.index}`}
{nft.collectionName}
-
+
)}
@@ -145,25 +162,27 @@ const ReviewSend = ({
- {selectedContact ? (
+ {isNewContact ? (
<>
- {selectedContact?.name}
-
- {shortAddress(selectedContact?.id)}
+
+ {contact?.icnsId || shortAddress(contact?.id)}
- >
- ) : (
- <>
- {shortAddress(to)}
{t('reviewSend.saveContact')}
>
+ ) : (
+ <>
+ {contact?.name}
+
+ {contact?.icnsId || shortAddress(contact?.id)}
+
+ >
)}
-
+
{token && (
@@ -192,6 +211,8 @@ const ReviewSend = ({
: t('reviewSend.holdToSend')
}
loading={loading}
+ disabled={disabled}
+ onPress={() => {}}
onLongPress={onSend}
buttonStyle={styles.button}
/>
@@ -205,7 +226,13 @@ const ReviewSend = ({
>
)}
-
+ {isNewContact && contact?.id && (
+
+ )}
);
};
diff --git a/src/screens/flows/Send/components/ReviewSend/styles.js b/src/screens/flows/Send/components/ReviewSend/styles.ts
similarity index 75%
rename from src/screens/flows/Send/components/ReviewSend/styles.js
rename to src/screens/flows/Send/components/ReviewSend/styles.ts
index 1fd93546..44ac98eb 100644
--- a/src/screens/flows/Send/components/ReviewSend/styles.js
+++ b/src/screens/flows/Send/components/ReviewSend/styles.ts
@@ -36,10 +36,15 @@ export default StyleSheet.create({
marginTop: 4,
color: Colors.ActionBlue,
},
- title: {
- ...fontMaker({ size: 24, weight: SEMIBOLD, color: Colors.White.Primary }),
- },
- subtitle: {
- ...fontMaker({ size: 16, weight: MEDIUM, color: Colors.White.Secondary }),
+ title: fontMaker({ size: 24, weight: SEMIBOLD, color: Colors.White.Primary }),
+ subtitle: fontMaker({
+ size: 16,
+ weight: MEDIUM,
+ color: Colors.White.Secondary,
+ }),
+ nft: {
+ height: 45,
+ width: 45,
+ borderRadius: 10,
},
});
diff --git a/src/screens/flows/Send/components/SaveContact/index.tsx b/src/screens/flows/Send/components/SaveContact/index.tsx
index 7e421304..47627aca 100644
--- a/src/screens/flows/Send/components/SaveContact/index.tsx
+++ b/src/screens/flows/Send/components/SaveContact/index.tsx
@@ -11,6 +11,7 @@ import TextInput from '@/commonComponents/TextInput';
import RainbowButton from '@/components/buttons/RainbowButton';
import Text from '@/components/common/Text';
import { FontStyles } from '@/constants/theme';
+import { Contact } from '@/interfaces/redux';
import { useAppDispatch, useAppSelector } from '@/redux/hooks';
import { addContact } from '@/redux/slices/user';
@@ -19,9 +20,10 @@ import styles from './styles';
interface Props {
modalRef: RefObject;
id: string;
+ onSaved?: (contact: Contact) => void;
}
-function SaveContact({ modalRef, id }: Props) {
+function SaveContact({ modalRef, id, onSaved }: Props) {
const dispatch = useAppDispatch();
const { contactsLoading, contacts } = useAppSelector(state => state.user);
@@ -31,6 +33,11 @@ function SaveContact({ modalRef, id }: Props) {
const savedContactName = contacts.find(c => c.name === name);
const title = t('saveContact.title');
+ const handleSaved = (contact: Contact) => {
+ onSaved?.(contact);
+ modalRef.current?.close();
+ };
+
const handleClose = () => {
setName('');
setError(false);
@@ -55,7 +62,12 @@ function SaveContact({ modalRef, id }: Props) {
name,
image: randomEmoji,
},
- onFinish: () => modalRef.current?.close(),
+ onFinish: () =>
+ handleSaved({
+ id,
+ name,
+ image: randomEmoji,
+ }),
})
);
}
diff --git a/src/screens/flows/Send/components/TokenSection/index.js b/src/screens/flows/Send/components/TokenSection/index.tsx
similarity index 52%
rename from src/screens/flows/Send/components/TokenSection/index.js
rename to src/screens/flows/Send/components/TokenSection/index.tsx
index 4b10e1ae..2a96e539 100644
--- a/src/screens/flows/Send/components/TokenSection/index.js
+++ b/src/screens/flows/Send/components/TokenSection/index.tsx
@@ -6,23 +6,33 @@ import Text from '@/components/common/Text';
import TokenItem from '@/components/tokens/TokenItem';
import { ICNS_CANISTER_ID } from '@/constants/canister';
import { ENABLE_NFTS } from '@/constants/nfts';
+import { Asset } from '@/interfaces/redux';
import NftItem from '@/screens/tabs/components/NftItem';
+import { FormattedCollection } from '@/utils/assets';
import styles from './styles';
-const TokenSection = ({ tokens, nfts, onTokenPress, onNftPress }) => {
+interface Props {
+ tokens?: Asset[];
+ nfts?: FormattedCollection[];
+ onTokenPress: (token: Asset) => void;
+ onNftPress: (nft: FormattedCollection) => void;
+}
+
+const TokenSection = ({ tokens, nfts, onTokenPress, onNftPress }: Props) => {
const { t } = useTranslation();
- const handleOnOpenNFT = nft => () => {
+ const handleOnOpenNFT = (nft: FormattedCollection) => {
onNftPress(nft);
};
// Filter ICNS since it's not tradable
- const filteredNFTS = nfts.filter(nft => nft.canister !== ICNS_CANISTER_ID);
+ const filteredNFTS =
+ nfts?.filter(nft => nft.canister !== ICNS_CANISTER_ID) || [];
return (
<>
{t('common.tokens')}
- {tokens.map(token => (
+ {tokens?.map(token => (
{
{t('common.collectibles')}
- {filteredNFTS.map(item => (
-
- ))}
+ {filteredNFTS.map(item => {
+ return (
+ handleOnOpenNFT(item)}
+ url={item.url}
+ title={`${item?.collectionName} #${item.index}`}
+ titleStyle={styles.itemTitle}
+ canisterId={item.canister}
+ itemId={item.index}
+ containerStyle={styles.itemContainer}
+ />
+ );
+ })}
>
)}
diff --git a/src/screens/flows/Send/components/TokenSection/styles.js b/src/screens/flows/Send/components/TokenSection/styles.ts
similarity index 62%
rename from src/screens/flows/Send/components/TokenSection/styles.js
rename to src/screens/flows/Send/components/TokenSection/styles.ts
index 31d597f0..d9bbeccb 100644
--- a/src/screens/flows/Send/components/TokenSection/styles.js
+++ b/src/screens/flows/Send/components/TokenSection/styles.ts
@@ -1,11 +1,14 @@
import { StyleSheet } from 'react-native';
+import { Colors } from '@/constants/theme';
+
export default StyleSheet.create({
token: {
marginTop: 20,
},
nftText: {
marginTop: 25,
+ marginBottom: 8,
},
nft: {
margin: 10,
@@ -15,4 +18,11 @@ export default StyleSheet.create({
flexWrap: 'wrap',
justifyContent: 'space-between',
},
+ itemTitle: {
+ color: Colors.Gray.Pure,
+ },
+ itemContainer: {
+ marginVertical: 8,
+ width: '48%',
+ },
});
diff --git a/src/screens/flows/Send/index.js b/src/screens/flows/Send/index.js
deleted file mode 100644
index 86ed6d86..00000000
--- a/src/screens/flows/Send/index.js
+++ /dev/null
@@ -1,429 +0,0 @@
-import React, { useEffect, useMemo, useRef, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-import { ActivityIndicator, Keyboard, View } from 'react-native';
-
-import Header from '@/commonComponents/Header';
-import Modal, { modalOffset } from '@/commonComponents/Modal';
-import PasswordModal from '@/commonComponents/PasswordModal';
-import TextInput from '@/commonComponents/TextInput';
-import ActionButton from '@/components/common/ActionButton';
-import Text from '@/components/common/Text';
-import Touchable from '@/components/common/Touchable';
-import Icon from '@/components/icons';
-import { ADDRESS_TYPES } from '@/constants/addresses';
-import { TOKENS, USD_PER_TC } from '@/constants/assets';
-import { isAndroid } from '@/constants/platform';
-import XTC_OPTIONS from '@/constants/xtc';
-import useICNS from '@/hooks/useICNS';
-import useKeychain from '@/hooks/useKeychain';
-import { useAppDispatch, useAppSelector } from '@/redux/hooks';
-import { getICPPrice } from '@/redux/slices/icp';
-import {
- burnXtc,
- sendToken,
- setTransaction,
- transferNFT,
-} from '@/redux/slices/user';
-import { formatCollections } from '@/utils/assets';
-import {
- validateAccountId,
- validateCanisterId,
- validatePrincipalId,
-} from '@/utils/ids';
-
-import AmountSection from './components/AmountSection';
-import ContactSection from './components/ContactSection';
-import ReviewSend from './components/ReviewSend';
-import SaveContact from './components/SaveContact';
-import TokenSection from './components/TokenSection';
-import styles from './styles';
-
-const INITIAL_ADDRESS_INFO = {
- isValid: null,
- type: null,
- resolvedAddress: null,
-};
-
-function Send({ modalRef, nft, token, onSuccess }) {
- const { t } = useTranslation();
- const dispatch = useAppDispatch();
- const { isSensorAvailable, getPassword } = useKeychain();
- const { icpPrice } = useAppSelector(state => state.icp);
- const { currentWallet } = useAppSelector(state => state.keyring);
- const { assets, contacts, transaction, collections, usingBiometrics } =
- useAppSelector(state => state.user);
-
- const reviewRef = useRef(null);
- const saveContactRef = useRef(null);
- const passwordRef = useRef(null);
-
- const nfts = useMemo(
- () => (collections ? formatCollections(collections) : []),
- [collections]
- );
- const [address, setAddress] = useState(null);
- const [loading, setLoading] = useState(false);
- const [usdAmount, setUsdAmount] = useState(null);
- const [destination] = useState(XTC_OPTIONS.SEND);
- const [selectedNft, setSelectedNft] = useState(nft);
- const [tokenAmount, setTokenAmount] = useState(null);
- const [selectedToken, setSelectedToken] = useState(null);
- const [selectedContact, setSelectedContact] = useState(null);
- const [selectedTokenPrice, setSelectedTokenPrice] = useState(null);
- const [addressInfo, setAddressInfo] = useState(INITIAL_ADDRESS_INFO);
- const [sendingXTCtoCanister, setSendingXTCtoCanister] = useState(false);
- const [biometricsError, setBiometricsError] = useState(false);
- const isValidAddress = addressInfo.isValid;
- const to = addressInfo.resolvedAddress || address || selectedContact?.id;
-
- const {
- loading: loadingICNS,
- resolvedAddress,
- isValid: isValidICNS,
- } = useICNS(address || selectedContact?.id, selectedToken?.symbol);
-
- useEffect(() => {
- const savedContact = contacts?.find(c => c.id === address);
- const isMySelf =
- address === currentWallet?.principal ||
- address === currentWallet?.accountId;
-
- if (savedContact && !isMySelf) {
- setSelectedContact(savedContact);
- }
- }, [contacts, address]);
-
- useEffect(() => {
- dispatch(getICPPrice());
- }, []);
-
- const handleBiometricsFail = err => {
- if (err.includes('locked')) {
- setBiometricsError(true);
- }
- passwordRef.current?.open();
- };
-
- const onContactPress = contact => {
- Keyboard.dismiss();
- setAddress(null);
- setSelectedContact(contact);
- };
-
- const onTokenPress = pressedToken => {
- setSelectedToken(pressedToken);
- setSelectedNft(null);
- };
-
- const onNftPress = pressedNFT => {
- setSelectedNft(pressedNFT);
- setSelectedToken(null);
- onReview();
- };
-
- const resetState = () => {
- setAddress(null);
- setAddressInfo(INITIAL_ADDRESS_INFO);
- setSelectedNft(null);
- setSelectedToken(null);
- setSelectedContact(null);
- setUsdAmount(null);
- setTokenAmount(null);
- dispatch(setTransaction(null));
- };
-
- const onError = () => {
- resetState();
- modalRef.current?.close();
- };
-
- const partialReset = () => {
- setSelectedNft(null);
- };
-
- const onChangeText = text => {
- setSelectedContact(null);
- setAddress(text);
- };
-
- const onReview = () => {
- Keyboard.dismiss();
- reviewRef.current?.open();
- };
-
- const handleSendNFT = () => {
- dispatch(
- transferNFT({
- to,
- nft: selectedNft,
- icpPrice,
- onEnd: () => setLoading(false),
- })
- );
- };
-
- const handleSendToken = () => {
- const amount = tokenAmount.value;
- if (sendingXTCtoCanister && destination === XTC_OPTIONS.BURN) {
- dispatch(burnXtc({ to, amount: amount.toString() }));
- } else {
- dispatch(
- sendToken({
- to,
- amount,
- canisterId: selectedToken?.canisterId,
- icpPrice,
- opts: {
- fee:
- selectedToken?.fee && selectedToken?.decimals
- ? selectedToken.fee * Math.pow(10, selectedToken.decimals)
- : 0, // TODO: Change this to selectedToken.fee only when dab is ready
- },
- })
- )
- .unwrap()
- .then(() => {
- setLoading(false);
- })
- .catch(() => setLoading(false));
- }
- };
-
- const send = () => {
- setLoading(true);
- const handler = selectedNft ? handleSendNFT : handleSendToken;
- handler();
- };
-
- const validateWithBiometrics = async () => {
- setLoading(true);
- const isBiometricsAvailable = await isSensorAvailable(handleBiometricsFail);
- if (isBiometricsAvailable) {
- const biometricsPassword = await getPassword(handleBiometricsFail);
- setLoading(false);
- return !!biometricsPassword;
- }
- setLoading(false);
- return false;
- };
-
- const handleSend = async () => {
- let authorized = false;
- if (usingBiometrics) {
- if (biometricsError) {
- passwordRef.current?.open();
- } else {
- authorized = await validateWithBiometrics();
- if (authorized) {
- send();
- }
- }
- } else {
- send();
- }
- };
-
- useEffect(() => {
- if (!selectedToken && nft) {
- setSelectedNft(nft);
- }
- }, [nft, isValidAddress]);
-
- useEffect(() => {
- if (!selectedNft && token) {
- setSelectedToken(token);
- }
- }, [token, isValidAddress]);
-
- useEffect(() => {
- if (selectedNft && (isValidAddress || selectedContact)) {
- onReview();
- }
- }, [selectedNft, isValidAddress, selectedContact]);
-
- useEffect(() => {
- if (selectedToken) {
- const price =
- { ICP: icpPrice, XTC: USD_PER_TC, WTC: USD_PER_TC, WICP: icpPrice }[
- selectedToken?.symbol
- ] || null;
- setSelectedTokenPrice(price);
- }
- }, [selectedToken]);
-
- useEffect(() => {
- if (address || selectedContact) {
- const id = resolvedAddress || address || selectedContact.id;
- const isUserAddress = [
- currentWallet?.principal,
- currentWallet?.accountId,
- ].includes(id);
- let isValid =
- !isUserAddress &&
- (validatePrincipalId(id) || validateAccountId(id) || isValidICNS);
-
- const type = validatePrincipalId(id)
- ? ADDRESS_TYPES.PRINCIPAL
- : ADDRESS_TYPES.ACCOUNT;
- if (
- type === ADDRESS_TYPES.ACCOUNT &&
- selectedToken?.symbol &&
- selectedToken.symbol !== TOKENS.ICP.symbol
- ) {
- isValid = false;
- }
- setAddressInfo({ isValid, type, resolvedAddress });
- setSendingXTCtoCanister(
- selectedToken?.symbol === TOKENS.XTC.symbol && validateCanisterId(id)
- );
- }
- }, [address, selectedContact, selectedToken, isValidICNS, resolvedAddress]);
-
- const availableAmount = useMemo(
- () => selectedToken?.amount - selectedToken?.fee,
- [selectedToken]
- );
-
- const availableUsdAmount = useMemo(
- () => (selectedTokenPrice ? availableAmount * selectedTokenPrice : null),
- [availableAmount, selectedTokenPrice]
- );
-
- const handleBack = () => {
- setAddress(null);
- setSelectedContact(null);
- setAddressInfo(INITIAL_ADDRESS_INFO);
- };
-
- const tokens = useMemo(
- // TODO: Add OGY when is available on Mobile.
- () =>
- addressInfo?.type === ADDRESS_TYPES.ACCOUNT
- ? assets.filter(asset => asset.symbol === TOKENS.ICP.symbol)
- : assets,
- [assets, addressInfo]
- );
-
- return (
-
-
- )
- }
- center={{t('send.title')}}
- />
- {t('send.inputLabel')}
- }
- right={
- !selectedContact && isValidAddress ? (
-
-
-
- ) : loadingICNS ? (
-
- ) : null
- }
- />
- >
- }>
-
- {!isValidAddress && (
-
- )}
- {isValidAddress && !selectedToken && (
-
- )}
- {isValidAddress && selectedToken && (
-
- )}
-
- {
- modalRef.current?.close();
- onSuccess?.();
- }}
- onClose={partialReset}
- transaction={transaction}
- loading={loading || loadingICNS}
- />
-
-
-
- );
-}
-
-export default Send;
diff --git a/src/screens/flows/Send/index.tsx b/src/screens/flows/Send/index.tsx
new file mode 100644
index 00000000..ae11c1f2
--- /dev/null
+++ b/src/screens/flows/Send/index.tsx
@@ -0,0 +1,389 @@
+import { t } from 'i18next';
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import { ActivityIndicator, Keyboard, View } from 'react-native';
+import { ScrollView } from 'react-native-gesture-handler';
+import { Modalize } from 'react-native-modalize';
+
+import { PasswordModal, Text, TextInput, Touchable } from '@/components/common';
+import Icon from '@/components/icons';
+import { TOKENS, USD_PER_TC } from '@/constants/assets';
+import useICNS from '@/hooks/useICNS';
+import useKeychain from '@/hooks/useKeychain';
+import { ModalScreenProps } from '@/interfaces/navigation';
+import { Asset, Contact } from '@/interfaces/redux';
+import Routes from '@/navigation/Routes';
+import { useAppDispatch, useAppSelector } from '@/redux/hooks';
+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,
+} from '@/utils/ids';
+
+import AmountSection from './components/AmountSection';
+import ContactSection from './components/ContactSection';
+import ReviewSend from './components/ReviewSend';
+import SaveContact from './components/SaveContact';
+import TokenSection from './components/TokenSection';
+import { Amount } from './interfaces';
+import styles from './styles';
+
+export interface Receiver {
+ id: string; // PID or AccID address
+ name?: string;
+ image?: string;
+ icnsId?: string;
+ isValid?: boolean;
+}
+
+function Send({ route }: ModalScreenProps) {
+ const dispatch = useAppDispatch();
+ const { token, nft } = route?.params || {};
+ const { isSensorAvailable, getPassword } = useKeychain();
+ const { icpPrice } = useAppSelector(state => state.icp);
+ const { currentWallet } = useAppSelector(state => state.keyring);
+
+ const { assets, contacts, transaction, collections, usingBiometrics } =
+ useAppSelector(state => state.user);
+
+ const reviewRef = useRef(null);
+ const saveContactRef = useRef(null);
+ const passwordRef = useRef(null);
+ const scrollViewRef = useRef(null);
+
+ const nfts = useMemo(
+ () => (collections ? formatCollections(collections) : []),
+ [collections]
+ );
+
+ const formattedNFT = useMemo(() => {
+ if (!nft) {
+ return undefined;
+ }
+ return nfts.find(n => n.canister === nft.canister && n.index === nft.index);
+ }, [nfts]);
+ const [loading, setLoading] = useState(false);
+ const [selectedNft, setSelectedNft] = useState<
+ FormattedCollection | undefined
+ >(formattedNFT);
+
+ const [tokenAmount, setTokenAmount] = useState();
+ const [usdAmount, setUsdAmount] = useState();
+ const [selectedToken, setSelectedToken] = useState(token);
+ const [selectedTokenPrice, setSelectedTokenPrice] = useState();
+ const [biometricsError, setBiometricsError] = useState(false);
+ const [receiver, setReceiver] = useState();
+
+ const isValidAddress = receiver?.isValid;
+ const showContacts = !receiver?.isValid;
+ const showTokens = receiver?.isValid && !selectedToken;
+ const showAmountSelector = receiver?.isValid && selectedToken;
+ const isNewContact =
+ receiver?.isValid &&
+ !contacts?.find(contact =>
+ receiver?.icnsId
+ ? contact.id === receiver?.icnsId
+ : contact.id === receiver?.id
+ );
+
+ const availableAmount = selectedToken
+ ? selectedToken?.amount - selectedToken?.fee
+ : 0;
+
+ const availableUsdAmount = selectedTokenPrice
+ ? availableAmount * selectedTokenPrice
+ : undefined;
+
+ const tokens = useMemo(
+ () =>
+ receiver?.id && validateAccountId(receiver.id)
+ ? assets.filter(asset => asset.symbol === TOKENS.ICP.symbol) // Use only ICP if receiver is an account
+ : assets,
+ [assets, receiver]
+ );
+
+ const {
+ loading: loadingICNS,
+ address: resolvedName,
+ resolvedAddress,
+ isValid: isValidICNS,
+ } = useICNS(
+ receiver?.icnsId ? undefined : receiver?.id,
+ selectedToken?.symbol
+ );
+
+ useEffect(() => {
+ dispatch(getICPPrice());
+ return () => {
+ dispatch(setTransaction(null));
+ };
+ }, []);
+
+ useEffect(() => {
+ if (selectedNft && receiver?.isValid) {
+ onReview();
+ }
+ }, [selectedNft, isValidAddress, receiver]);
+
+ useEffect(() => {
+ if (selectedToken) {
+ const price =
+ { ICP: icpPrice, XTC: USD_PER_TC, WTC: USD_PER_TC, WICP: icpPrice }[
+ selectedToken?.symbol
+ ] || undefined;
+ setSelectedTokenPrice(price);
+ }
+ }, [selectedToken, icpPrice]);
+
+ /** Checks if the ICNS hook returns valid info and updates the receiver state */
+ useEffect(() => {
+ if (
+ isValidICNS &&
+ resolvedName &&
+ validateICNSName(resolvedName) &&
+ currentWallet?.icnsData?.reverseResolvedName !== resolvedName
+ ) {
+ const savedContact = contacts?.find(c => c.id === resolvedName);
+ setReceiver({
+ id: resolvedAddress!,
+ name: savedContact?.name || resolvedName,
+ image: savedContact?.image,
+ icnsId: resolvedName,
+ isValid: true,
+ });
+ }
+ }, [isValidICNS, resolvedAddress, resolvedName, contacts]);
+
+ const scrollToTop = () => {
+ scrollViewRef.current?.scrollTo({ x: 0, y: 0, animated: false });
+ };
+
+ const handleBiometricsFail = (err: string) => {
+ if (err.includes('locked')) {
+ setBiometricsError(true);
+ }
+ passwordRef.current?.open();
+ };
+
+ const handleContactPress = (contact: Contact) => {
+ Keyboard.dismiss();
+ setReceiver({
+ ...contact,
+ isValid: true,
+ });
+ scrollToTop();
+ };
+
+ const handleTokenPress = (pressedToken: Asset) => {
+ setSelectedToken(pressedToken);
+ setSelectedNft(undefined);
+ scrollToTop();
+ };
+
+ const handleNftPress = (pressedNFT: FormattedCollection) => {
+ setSelectedNft(pressedNFT);
+ setSelectedToken(undefined);
+ onReview();
+ };
+
+ const onChangeText = (text: string) => {
+ if (!text) {
+ return setReceiver(undefined);
+ }
+
+ const savedContact = contacts?.find(c => c.id === text);
+ const isOwn = isOwnAddress(text, currentWallet!);
+
+ if (savedContact && !isOwn) {
+ setReceiver({
+ ...savedContact,
+ isValid: true,
+ });
+ scrollToTop();
+ } else {
+ const isValid =
+ !isOwn && (validatePrincipalId(text) || validateAccountId(text));
+ setReceiver({ id: text, isValid });
+ }
+ };
+
+ const onReview = () => {
+ Keyboard.dismiss();
+ reviewRef.current?.open();
+ };
+
+ const handleSendNFT = () => {
+ if (selectedNft && receiver?.id) {
+ setLoading(true);
+ dispatch(
+ transferNFT({
+ to: receiver.id,
+ nft: selectedNft,
+ icpPrice,
+ onSuccess: () => setLoading(false),
+ onFailure: () => setLoading(false),
+ })
+ );
+ }
+ };
+
+ const handleSendToken = () => {
+ if (tokenAmount && receiver?.id && selectedToken) {
+ setLoading(true);
+ const amount = tokenAmount.value;
+ dispatch(
+ sendToken({
+ to: receiver.id,
+ amount,
+ canisterId: selectedToken?.canisterId,
+ icpPrice,
+ opts: {
+ fee:
+ selectedToken?.fee && selectedToken?.decimals
+ ? selectedToken.fee * Math.pow(10, selectedToken.decimals)
+ : 0, // TODO: Change this to selectedToken.fee only when dab is ready
+ },
+ onSuccess: () => setLoading(false),
+ onFailure: () => setLoading(false),
+ })
+ );
+ }
+ };
+
+ const send = () => {
+ const handler = selectedNft ? handleSendNFT : handleSendToken;
+ handler();
+ };
+
+ const validateWithBiometrics = async () => {
+ setLoading(true);
+ const isBiometricsAvailable = await isSensorAvailable(handleBiometricsFail);
+ if (isBiometricsAvailable) {
+ const biometricsPassword = await getPassword(handleBiometricsFail);
+ setLoading(false);
+ return !!biometricsPassword;
+ }
+ setLoading(false);
+ return false;
+ };
+
+ const handleSend = async () => {
+ let authorized = false;
+ if (usingBiometrics) {
+ if (biometricsError) {
+ passwordRef.current?.open();
+ } else {
+ authorized = await validateWithBiometrics();
+ if (authorized) {
+ send();
+ }
+ }
+ } else {
+ send();
+ }
+ };
+
+ const handleContactSaved = ({ id, name, image }: Contact) => {
+ setReceiver({
+ id: validateICNSName(id) && receiver?.id ? receiver.id : id, // If the contact is an ICNS, we keep the previous resolved address
+ name,
+ image,
+ icnsId: receiver?.icnsId,
+ isValid: true,
+ });
+ };
+
+ return (
+
+ {t('send.inputLabel')}}
+ right={
+ isNewContact ? (
+
+
+
+ ) : loadingICNS ? (
+
+ ) : null
+ }
+ />
+
+ {showContacts && (
+
+ )}
+ {showTokens && (
+
+ )}
+ {showAmountSelector && (
+
+ )}
+
+ setSelectedNft(undefined)}
+ onContactSaved={handleContactSaved}
+ transaction={transaction}
+ loading={loading}
+ disabled={loadingICNS}
+ />
+ {isNewContact && receiver && (
+
+ )}
+
+
+ );
+}
+
+export default Send;
diff --git a/src/screens/flows/Send/styles.js b/src/screens/flows/Send/styles.ts
similarity index 92%
rename from src/screens/flows/Send/styles.js
rename to src/screens/flows/Send/styles.ts
index fd750341..24faae22 100644
--- a/src/screens/flows/Send/styles.js
+++ b/src/screens/flows/Send/styles.ts
@@ -5,6 +5,10 @@ import { Colors, FontStyles } from '@/constants/theme';
import { fontMaker } from '@/utils/fonts';
export default StyleSheet.create({
+ container: {
+ flex: 1,
+ marginBottom: 30,
+ },
contactItem: {
marginTop: 15,
},
diff --git a/src/screens/flows/Send/utils.ts b/src/screens/flows/Send/utils.ts
index 2178b433..105272aa 100644
--- a/src/screens/flows/Send/utils.ts
+++ b/src/screens/flows/Send/utils.ts
@@ -4,7 +4,7 @@ import { TOKENS, USD_PER_TC } from '@/constants/assets';
export const getFeePrice = (
tokenSymbol: string,
icpPrice: number,
- tokenFee: number
+ tokenFee: number = 0
) => {
switch (tokenSymbol) {
case TOKENS.ICP.symbol:
diff --git a/src/screens/flows/WalletConnect/screens/Flows/components/ActionItem/index.tsx b/src/screens/flows/WalletConnect/screens/Flows/components/ActionItem/index.tsx
index 2fa48e5c..ac0fd273 100644
--- a/src/screens/flows/WalletConnect/screens/Flows/components/ActionItem/index.tsx
+++ b/src/screens/flows/WalletConnect/screens/Flows/components/ActionItem/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React from 'react';
import { View } from 'react-native';
import ImageDisplayer from '@/components/common/NftDisplayer/components/ImageDisplayer';
@@ -15,8 +15,7 @@ export interface Props {
}
function ActionItem({ title, subtitle, iconUrl }: Props) {
- const [imageType, setImageType] = useState('');
- useGetType(iconUrl, setImageType);
+ const imageType = useGetType(iconUrl);
return (
diff --git a/src/screens/flows/WalletConnect/screens/Flows/components/RequestCall/index.tsx b/src/screens/flows/WalletConnect/screens/Flows/components/RequestCall/index.tsx
index 67d4e09b..580d6323 100644
--- a/src/screens/flows/WalletConnect/screens/Flows/components/RequestCall/index.tsx
+++ b/src/screens/flows/WalletConnect/screens/Flows/components/RequestCall/index.tsx
@@ -116,7 +116,7 @@ function RequestCall(props: Props) {
- {t('walletConnect.learnMore')}
+ {t('common.learnMore')}
)}
diff --git a/src/screens/flows/WalletConnect/screens/InitialConnection/index.tsx b/src/screens/flows/WalletConnect/screens/InitialConnection/index.tsx
index 8f343a73..d963589c 100644
--- a/src/screens/flows/WalletConnect/screens/InitialConnection/index.tsx
+++ b/src/screens/flows/WalletConnect/screens/InitialConnection/index.tsx
@@ -9,7 +9,7 @@ import RainbowButton from '@/components/buttons/RainbowButton';
import Icon from '@/components/icons';
import { FontStyles } from '@/constants/theme';
import useDisableBack from '@/hooks/useDisableBack';
-import { ScreenProps } from '@/interfaces/navigation';
+import { RootScreenProps } from '@/interfaces/navigation';
import { Container } from '@/layout';
import Routes from '@/navigation/Routes';
import { useAppDispatch, useAppSelector } from '@/redux/hooks';
@@ -26,7 +26,7 @@ import styles from './styles';
function WCInitialConnection({
route,
navigation,
-}: ScreenProps) {
+}: RootScreenProps) {
useDisableBack();
const modalRef = useRef(null);
const dispatch = useAppDispatch();
diff --git a/src/screens/flows/WalletConnect/screens/TimeoutError/index.tsx b/src/screens/flows/WalletConnect/screens/TimeoutError/index.tsx
index cc336dea..e6dddabf 100644
--- a/src/screens/flows/WalletConnect/screens/TimeoutError/index.tsx
+++ b/src/screens/flows/WalletConnect/screens/TimeoutError/index.tsx
@@ -6,7 +6,7 @@ import Button from '@/components/buttons/Button';
import Text from '@/components/common/Text';
import { FontStyles } from '@/constants/theme';
import useDisableBack from '@/hooks/useDisableBack';
-import { ScreenProps } from '@/interfaces/navigation';
+import { RootScreenProps } from '@/interfaces/navigation';
import { Container } from '@/layout';
import Routes from '@/navigation/Routes';
@@ -15,7 +15,7 @@ import styles from './styles';
function WCTimeoutError({
route,
navigation,
-}: ScreenProps) {
+}: RootScreenProps) {
useDisableBack();
const { dappUrl, dappName } = route?.params || {};
@@ -28,7 +28,9 @@ function WCTimeoutError({
index: 1,
routes: [{ name: Routes.SWIPE_LAYOUT }],
});
- gotoDapp();
+ if (dappUrl) {
+ gotoDapp();
+ }
};
return (
diff --git a/src/screens/tabs/NFTs/index.js b/src/screens/tabs/NFTs/index.js
deleted file mode 100644
index 067c5db4..00000000
--- a/src/screens/tabs/NFTs/index.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import { useScrollToTop } from '@react-navigation/native';
-import { FlashList } from '@shopify/flash-list';
-import React, { useMemo, useRef } from 'react';
-import { useTranslation } from 'react-i18next';
-import { RefreshControl, View } from 'react-native';
-
-import EmptyState from '@/commonComponents/EmptyState';
-import ErrorState from '@/commonComponents/ErrorState';
-import Text from '@/components/common/Text';
-import { ERROR_TYPES } from '@/constants/general';
-import { Colors } from '@/constants/theme';
-import { useStateWithCallback } from '@/hooks/useStateWithCallback';
-import { Container, Separator } from '@/layout';
-import { useAppDispatch, useAppSelector } from '@/redux/hooks';
-import { getNFTs } from '@/redux/slices/user';
-import NftItem, { ITEM_HEIGHT } from '@/screens/tabs/components/NftItem';
-import WalletHeader from '@/screens/tabs/components/WalletHeader';
-import { formatCollections } from '@/utils/assets';
-
-import NftDetail from './screens/NftDetail';
-import styles from './styles';
-function NFTs() {
- const { t } = useTranslation();
- const detailRef = useRef(null);
- const NFTListRef = useRef(null);
- useScrollToTop(NFTListRef);
- const dispatch = useAppDispatch();
- const [selectedNft, setSelectedNft] = useStateWithCallback(null);
- const { collections, collectionsError, collectionsLoading } = useAppSelector(
- state => state.user
- );
-
- const nfts = useMemo(
- () => (collections ? formatCollections(collections) : []),
- [collections]
- );
-
- const renderNFT = ({ item }) => ;
-
- const onOpen = nft => () => {
- setSelectedNft(nft, () => detailRef.current?.open());
- };
-
- const onRefresh = () => {
- dispatch(getNFTs());
- };
-
- return (
- <>
-
-
- {t('common.collectibles')}
-
- {!collectionsError ? (
-
- `${index}${canister}`}
- showsVerticalScrollIndicator={false}
- contentContainerStyle={styles.nftsContainer}
- overScrollMode="never"
- refreshing={collectionsLoading}
- refreshControl={
-
- }
- ListEmptyComponent={
-
- }
- />
-
- ) : (
-
- )}
-
- setSelectedNft(null)}
- />
- >
- );
-}
-
-export default NFTs;
diff --git a/src/screens/tabs/NFTs/index.tsx b/src/screens/tabs/NFTs/index.tsx
new file mode 100644
index 00000000..0b1917ba
--- /dev/null
+++ b/src/screens/tabs/NFTs/index.tsx
@@ -0,0 +1,126 @@
+import { useScrollToTop } from '@react-navigation/native';
+import { FlashList } from '@shopify/flash-list';
+import React, { useMemo, useRef } from 'react';
+import { useTranslation } from 'react-i18next';
+import { RefreshControl, View } from 'react-native';
+
+import useScrollHanlder from '@/components/buttons/ScrollableButton/hooks/useScrollHandler';
+import { EmptyState, ErrorState, Text } from '@/components/common';
+import { ERROR_TYPES } from '@/constants/general';
+import { Colors } from '@/constants/theme';
+import { RootScreenProps } from '@/interfaces/navigation';
+import { Collection } from '@/interfaces/redux';
+import { Container, Separator } from '@/layout';
+import Routes from '@/navigation/Routes';
+import { useAppDispatch, useAppSelector } from '@/redux/hooks';
+import { getNFTs } from '@/redux/slices/user';
+import WalletHeader from '@/screens/tabs/components/WalletHeader';
+import { isICNSCanister } from '@/utils/assets';
+
+import NftItem, { ITEM_HEIGHT } from '../components/NftItem';
+import AddCollection from './modals/AddCollection';
+import styles from './styles';
+
+function NFTs({ navigation }: RootScreenProps) {
+ const { t } = useTranslation();
+ const NFTListRef = useRef(null);
+ useScrollToTop(NFTListRef);
+ const dispatch = useAppDispatch();
+ const { collections, collectionsError, collectionsLoading } = useAppSelector(
+ state => state.user
+ );
+ const { handleOnScroll, scrollPosition } = useScrollHanlder();
+
+ const totalNfts = useMemo(() => {
+ return collections.reduce((acc, curr) => acc + curr.tokens.length, 0);
+ }, [collections]);
+
+ const handleCollectionPress = (collection: Collection) => {
+ if (collection.tokens.length === 1) {
+ navigation.navigate(Routes.MODAL_STACK, {
+ screen: Routes.NFT_DETAIL,
+ params: {
+ index: collection.tokens[0].index,
+ canisterId: collection.canisterId,
+ },
+ });
+ } else {
+ navigation.navigate(Routes.MODAL_STACK, {
+ screen: Routes.NFT_LIST,
+ params: { canisterId: collection.canisterId },
+ });
+ }
+ };
+
+ const renderCollection = ({ item }: { item: Collection }) => {
+ const isICNS = isICNSCanister(item.canisterId);
+ return (
+ handleCollectionPress(item)}
+ canisterId={item.canisterId}
+ itemId={item.name}
+ />
+ );
+ };
+
+ const onRefresh = () => {
+ dispatch(getNFTs({ refresh: true }));
+ };
+
+ return (
+ <>
+
+
+
+ {t('common.collectibles')}
+
+ {t('nftTab.items', { count: totalNfts })}
+
+
+
+ {!collectionsError ? (
+
+ canisterId}
+ showsVerticalScrollIndicator={false}
+ contentContainerStyle={styles.nftsContainer}
+ overScrollMode="never"
+ refreshing={collectionsLoading}
+ refreshControl={
+
+ }
+ ListEmptyComponent={
+
+ }
+ />
+
+
+ ) : (
+
+ )}
+
+ >
+ );
+}
+
+export default NFTs;
diff --git a/src/screens/tabs/NFTs/modals/AddCollection/components/CustomCollection/index.tsx b/src/screens/tabs/NFTs/modals/AddCollection/components/CustomCollection/index.tsx
new file mode 100644
index 00000000..355d419c
--- /dev/null
+++ b/src/screens/tabs/NFTs/modals/AddCollection/components/CustomCollection/index.tsx
@@ -0,0 +1,158 @@
+import { t } from 'i18next';
+import React, { useCallback, useMemo, useRef, useState } from 'react';
+import { Keyboard, Linking, View } from 'react-native';
+import { Modalize } from 'react-native-modalize';
+
+import Button from '@/components/buttons/Button';
+import RainbowButton from '@/components/buttons/RainbowButton';
+import { ActionSheet, Text, TextInput } from '@/components/common';
+import Icon from '@/components/icons';
+import { Colors } from '@/constants/theme';
+import { custonNFTsUrl } from '@/constants/urls';
+import { CollectionInfo, NonFungibleStandard } from '@/interfaces/keyring';
+import { useAppDispatch } from '@/redux/hooks';
+import { getCollectionInfo } from '@/redux/slices/user';
+import { validateCanisterId } from '@/utils/ids';
+
+import styles, { iconColor } from './styles';
+interface Props {
+ setSelectedCollection: (collectionInfo: CollectionInfo) => void;
+}
+
+function CustomCollection({ setSelectedCollection }: Props) {
+ const dispatch = useAppDispatch();
+ const optionsRef = useRef(null);
+
+ const [loading, setLoading] = useState(false);
+ const [collectionError, setCollectionError] = useState(false);
+ const [canisterId, setCanisterId] = useState('');
+ const [canisterIdError, setCanisterIdError] = useState(false);
+ const [standard, setStandard] = useState('DIP721');
+
+ const error = canisterIdError || collectionError;
+
+ const handleIdChange = (text: string) => {
+ setCanisterId(text);
+ setCanisterIdError(!validateCanisterId(text));
+ setCollectionError(false);
+ };
+
+ const clearValues = () => {
+ setLoading(false);
+ setCanisterId('');
+ setStandard('DIP721');
+ setCollectionError(false);
+ setCanisterIdError(false);
+ };
+
+ const handleStandardChange = useCallback((selected: NonFungibleStandard) => {
+ setStandard(selected);
+ setCollectionError(false);
+ }, []);
+
+ const handleStandardPress = () => {
+ Keyboard.dismiss();
+ optionsRef?.current?.open();
+ };
+
+ const standardList = useMemo(
+ () => [
+ {
+ id: 1,
+ label: 'DIP721',
+ onPress: () => handleStandardChange('DIP721'),
+ },
+ ],
+ [handleStandardChange]
+ );
+
+ const handleSubmit = () => {
+ Keyboard.dismiss();
+ if (validateCanisterId(canisterId)) {
+ setLoading(true);
+ setCanisterIdError(false);
+ dispatch(
+ getCollectionInfo({
+ collection: { canisterId, standard },
+ onSuccess: collection => {
+ setSelectedCollection(collection);
+ clearValues();
+ },
+ onFailure: () => {
+ setCollectionError(true);
+ setLoading(false);
+ },
+ })
+ );
+ } else {
+ setCanisterIdError(true);
+ }
+ };
+
+ const handleLinkPress = () => {
+ Linking.canOpenURL(custonNFTsUrl).then(() =>
+ Linking.openURL(custonNFTsUrl)
+ );
+ };
+
+ return (
+
+
+ {error && (
+
+
+
+ {collectionError
+ ? t('addCollection.canisterNotCompatible', { standard })
+ : t('addCollection.invalidCanisterId')}
+ {canisterIdError && (
+
+ {t('common.learnMore')}
+
+ )}
+
+
+ )}
+
+
+
+
+ {t('addCollection.customCaption')}
+
+
+
+
+
+ );
+}
+
+export default CustomCollection;
diff --git a/src/screens/tabs/NFTs/modals/AddCollection/components/CustomCollection/styles.ts b/src/screens/tabs/NFTs/modals/AddCollection/components/CustomCollection/styles.ts
new file mode 100644
index 00000000..8be7a024
--- /dev/null
+++ b/src/screens/tabs/NFTs/modals/AddCollection/components/CustomCollection/styles.ts
@@ -0,0 +1,61 @@
+import { StyleSheet } from 'react-native';
+
+import { SEMIBOLD } from '@/constants/fonts';
+import { Colors } from '@/constants/theme';
+import { fontMaker } from '@/utils/fonts';
+
+export const iconColor = Colors.White.Secondary;
+
+export default StyleSheet.create({
+ container: {
+ paddingHorizontal: 16,
+ },
+ standardButton: {
+ marginTop: 16,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingHorizontal: 20,
+ height: 56,
+ backgroundColor: Colors.Black.Primary,
+ },
+ standardTextPlaceholder: {
+ ...fontMaker({ size: 18, color: Colors.White.Secondary, weight: SEMIBOLD }),
+ },
+ standardText: {
+ ...fontMaker({ size: 18, color: Colors.White.Pure, weight: SEMIBOLD }),
+ },
+ standardIcon: {
+ transform: [{ rotate: '90deg' }],
+ },
+ button: {
+ marginBottom: 16,
+ marginTop: 40,
+ },
+ captionContainer: {
+ flexDirection: 'row',
+ marginTop: 8,
+ },
+ standardCaption: {
+ marginLeft: 4,
+ color: Colors.White.Secondary,
+ flex: 1,
+ },
+ errorContainer: {
+ flexDirection: 'row',
+ marginTop: 8,
+ },
+ errorText: {
+ marginLeft: 4,
+ color: Colors.Red,
+ flex: 1,
+ },
+ errorLink: {
+ textDecorationLine: 'underline',
+ },
+ optionsText: {
+ color: Colors.White.Primary,
+ },
+ cancelText: {
+ color: Colors.Red,
+ },
+});
diff --git a/src/screens/tabs/NFTs/modals/AddCollection/components/ReviewCollection/index.tsx b/src/screens/tabs/NFTs/modals/AddCollection/components/ReviewCollection/index.tsx
new file mode 100644
index 00000000..934585b2
--- /dev/null
+++ b/src/screens/tabs/NFTs/modals/AddCollection/components/ReviewCollection/index.tsx
@@ -0,0 +1,77 @@
+import { t } from 'i18next';
+import React from 'react';
+import { View } from 'react-native';
+
+import RainbowButton from '@/components/buttons/RainbowButton';
+import { Text } from '@/components/common';
+import Icon from '@/components/tokens/TokenIcon';
+import useCustomToast from '@/hooks/useCustomToast';
+import { CollectionInfo, NonFungibleStandard } from '@/interfaces/keyring';
+import { useAppDispatch, useAppSelector } from '@/redux/hooks';
+import { addCustomCollection } from '@/redux/slices/user';
+
+import styles from './styles';
+
+interface Props {
+ collection: CollectionInfo;
+ handleModalClose: () => void;
+}
+
+function ReviewCollection({ collection, handleModalClose }: Props) {
+ const { canisterId, standard, name } = collection;
+ const { showInfo, showError, showSuccess } = useCustomToast();
+ const dispatch = useAppDispatch();
+ const loading = useAppSelector(state => state.user.collectionsLoading);
+
+ const handleAddCollection = () => {
+ dispatch(
+ addCustomCollection({
+ nft: { canisterId, standard: standard as NonFungibleStandard },
+ onSuccess: () => {
+ handleModalClose();
+ showSuccess(
+ t('addCollection.successToastTitle'),
+ t('addCollection.successToastMessage')
+ );
+ },
+ onFailure: (e: string) => {
+ handleModalClose();
+ if (e.includes('The NFT is already registered')) {
+ showInfo(
+ t('addCollection.infoToastTitle'),
+ t('addCollection.infoToastMessage', {
+ name: name || t('addCollection.unknownCollection'),
+ })
+ );
+ } else {
+ showError(
+ t('addCollection.errorToastTitle'),
+ t('addCollection.errorToastMessage')
+ );
+ }
+ },
+ })
+ );
+ };
+
+ return (
+
+
+ {t('addCollection.safetyAlert')}
+
+
+
+
+ {collection.name || t('addCollection.noName')}
+
+
+
+
+ );
+}
+
+export default ReviewCollection;
diff --git a/src/screens/tabs/NFTs/modals/AddCollection/components/ReviewCollection/styles.ts b/src/screens/tabs/NFTs/modals/AddCollection/components/ReviewCollection/styles.ts
new file mode 100644
index 00000000..50a76c72
--- /dev/null
+++ b/src/screens/tabs/NFTs/modals/AddCollection/components/ReviewCollection/styles.ts
@@ -0,0 +1,31 @@
+import { StyleSheet } from 'react-native';
+
+import { Colors } from '@/constants/theme';
+import { fontMaker } from '@/utils/fonts';
+
+export default StyleSheet.create({
+ container: {
+ flex: 1,
+ paddingHorizontal: 20,
+ paddingBottom: 20,
+ },
+ alert: {
+ color: Colors.White.Secondary,
+ textAlign: 'center',
+ },
+ name: {
+ ...fontMaker({ color: Colors.White.Primary }),
+ marginLeft: 12,
+ },
+ collectionContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: 16,
+ paddingHorizontal: 10,
+ borderWidth: 1,
+ borderTopColor: Colors.Divider[1],
+ borderBottomColor: Colors.Divider[1],
+ marginTop: 36,
+ marginBottom: 40,
+ },
+});
diff --git a/src/screens/tabs/NFTs/modals/AddCollection/index.tsx b/src/screens/tabs/NFTs/modals/AddCollection/index.tsx
new file mode 100644
index 00000000..6e535b39
--- /dev/null
+++ b/src/screens/tabs/NFTs/modals/AddCollection/index.tsx
@@ -0,0 +1,76 @@
+import { t } from 'i18next';
+import React, { useRef, useState } from 'react';
+import { Modalize } from 'react-native-modalize';
+
+import ScrollableButton from '@/components/buttons/ScrollableButton';
+import { ActionButton, Header, Modal, Text } from '@/components/common';
+import { CollectionInfo } from '@/interfaces/keyring';
+
+import CustomCollection from './components/CustomCollection';
+import ReviewCollection from './components/ReviewCollection';
+import styles from './styles';
+
+interface Props {
+ scrollPosition: number;
+}
+
+function AddCollection({ scrollPosition }: Props) {
+ const modalRef = useRef(null);
+ const [selectedCollection, setSelectedCollection] =
+ useState();
+ const showReviewCollection = !!selectedCollection;
+
+ const handleModalClose = () => {
+ modalRef.current?.close();
+ cleanState();
+ };
+
+ const cleanState = () => setSelectedCollection(undefined);
+
+ return (
+ <>
+ modalRef?.current?.open()}
+ buttonStyle={styles.buttonContainer}
+ />
+
+ )
+ }
+ center={
+
+ {t('addCollection.customCollection')}
+
+ }
+ right={
+
+ }
+ />
+ }>
+ {showReviewCollection ? (
+
+ ) : (
+
+ )}
+
+ >
+ );
+}
+export default AddCollection;
diff --git a/src/screens/tabs/NFTs/modals/AddCollection/styles.ts b/src/screens/tabs/NFTs/modals/AddCollection/styles.ts
new file mode 100644
index 00000000..d8dcfdd6
--- /dev/null
+++ b/src/screens/tabs/NFTs/modals/AddCollection/styles.ts
@@ -0,0 +1,15 @@
+import { StyleSheet } from 'react-native';
+
+import { Colors } from '@/constants/theme';
+
+export default StyleSheet.create({
+ buttonContainer: {
+ position: 'absolute',
+ bottom: 16,
+ right: 10,
+ borderRadius: 100,
+ },
+ title: {
+ color: Colors.White.Primary,
+ },
+});
diff --git a/src/screens/tabs/NFTs/screens/NftDetail/components/Section/index.tsx b/src/screens/tabs/NFTs/screens/NftDetail/components/Section/index.tsx
index b2560117..b37ebfb7 100644
--- a/src/screens/tabs/NFTs/screens/NftDetail/components/Section/index.tsx
+++ b/src/screens/tabs/NFTs/screens/NftDetail/components/Section/index.tsx
@@ -8,7 +8,7 @@ import styles from './styles';
interface Props {
title: string;
children: React.ReactNode;
- style: StyleProp;
+ style?: StyleProp;
}
const Section = ({ title, children, style }: Props) => (
diff --git a/src/screens/tabs/NFTs/screens/NftDetail/index.js b/src/screens/tabs/NFTs/screens/NftDetail/index.js
deleted file mode 100644
index 55f10600..00000000
--- a/src/screens/tabs/NFTs/screens/NftDetail/index.js
+++ /dev/null
@@ -1,167 +0,0 @@
-import { t } from 'i18next';
-import React, { useEffect, useMemo, useRef, useState } from 'react';
-import { Linking, Platform, View } from 'react-native';
-
-import Badge from '@/commonComponents/Badge';
-import Header from '@/commonComponents/Header';
-import Modal from '@/commonComponents/Modal';
-import NftDisplayer from '@/commonComponents/NftDisplayer';
-import Button from '@/components/buttons/Button';
-import RainbowButton from '@/components/buttons/RainbowButton';
-import ActionSheet from '@/components/common/ActionSheet';
-import Text from '@/components/common/Text';
-import { ICNS_CANISTER_ID } from '@/constants/canister';
-import { FontStyles } from '@/constants/theme';
-import DownloadIcon from '@/icons/material/Download.svg';
-import ViewIcon from '@/icons/material/View.svg';
-import Send from '@/screens/flows/Send';
-import { getNFTDetails } from '@/services/DAB';
-import { downloadFile } from '@/utils/filesystem';
-import { getAbsoluteType } from '@/utils/fileTypes';
-import { deleteWhiteSpaces } from '@/utils/strings';
-
-import Section from './components/Section';
-import styles from './styles';
-
-function NftDetail({ modalRef, handleClose, selectedNFT, ...props }) {
- const {
- url,
- canister,
- index,
- standard,
- name,
- icon,
- collectionDescription,
- type,
- collectionName,
- } = selectedNFT || {};
- const isICNS = canister === ICNS_CANISTER_ID;
- const nftName = `${collectionName} #${index}`;
-
- const actionSheetRef = useRef(null);
- const [isDownloading, setIsDownloading] = useState(false);
- const [nftDetails, setNFTDetails] = useState(null);
-
- useEffect(() => {
- if (selectedNFT && !isICNS) {
- getNFTDetails({ index, canister, standard }).then(details =>
- setNFTDetails(details)
- );
- }
- return () => setNFTDetails(null);
- }, [selectedNFT]);
-
- const sendRef = useRef(null);
-
- const handleSend = () => {
- sendRef.current?.open();
- };
-
- const downloadNFT = () => {
- // TODO: Handle download error and permissons error
- setIsDownloading(true);
- downloadFile({
- filename: `NFT_${deleteWhiteSpaces(nftName)}${getAbsoluteType(
- selectedNFT?.type
- )}`,
- url: selectedNFT?.url,
- onFetched: () => setIsDownloading(false),
- });
- };
-
- const moreOptions = useMemo(
- () => ({
- title: t('nftDetail.moreTitle'),
- options: [
- {
- id: 1,
- label: t('nftDetail.moreOptions.view'),
- onPress: () => Linking.openURL(selectedNFT?.url),
- icon: Platform.select({ android: ViewIcon }),
- },
- {
- id: 2,
- label: t('nftDetail.moreOptions.download'),
- onPress: downloadNFT,
- icon: Platform.select({ android: DownloadIcon }),
- },
- ],
- }),
- [selectedNFT]
- );
-
- return (
- <>
-
-
- {isICNS ? name : index ? `#${index}` : ''}
-
- }
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
- {!!collectionDescription && (
-
- {collectionDescription}
-
- )}
- {!!nftDetails?.metadata?.properties?.length && (
-
- {nftDetails.metadata.properties.map(prop => (
-
- ))}
-
- )}
-
-
-
- modalRef.current?.close()}
- />
- >
- );
-}
-
-export default NftDetail;
diff --git a/src/screens/tabs/NFTs/screens/NftDetail/index.tsx b/src/screens/tabs/NFTs/screens/NftDetail/index.tsx
new file mode 100644
index 00000000..cf565f9e
--- /dev/null
+++ b/src/screens/tabs/NFTs/screens/NftDetail/index.tsx
@@ -0,0 +1,176 @@
+import { NFTDetails } from '@psychedelic/dab-js';
+import { StackHeaderProps } from '@react-navigation/stack';
+import { t } from 'i18next';
+import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
+import { Linking, Platform, View } from 'react-native';
+import { ScrollView } from 'react-native-gesture-handler';
+import { Modalize } from 'react-native-modalize';
+
+import Button from '@/components/buttons/Button';
+import RainbowButton from '@/components/buttons/RainbowButton';
+import { ActionSheet, Badge, NftDisplayer, Text } from '@/components/common';
+import ModalHeader from '@/components/navigation/ModalHeader';
+import { FontStyles } from '@/constants/theme';
+import { ICNS_URL } from '@/constants/urls';
+import useGetType from '@/hooks/useGetType';
+import DownloadIcon from '@/icons/material/Download.svg';
+import ViewIcon from '@/icons/material/View.svg';
+import { ModalScreenProps } from '@/interfaces/navigation';
+import Routes from '@/navigation/Routes';
+import { useAppSelector } from '@/redux/hooks';
+import { getNFTDetails } from '@/services/DAB';
+import { isICNSCanister } from '@/utils/assets';
+import { downloadFile } from '@/utils/filesystem';
+import { deleteWhiteSpaces } from '@/utils/strings';
+
+import Section from './components/Section';
+import styles from './styles';
+
+const header = (props: StackHeaderProps, showBack?: boolean) => (
+
+);
+
+function NftDetail({ route, navigation }: ModalScreenProps) {
+ const { canisterId, index, showBack } = route.params;
+ const { collections } = useAppSelector(state => state.user);
+ const collection = collections.find(c => c.canisterId === canisterId);
+ const selectedNFT = collection?.tokens?.find(token => token.index === index);
+ const isICNS = isICNSCanister(selectedNFT?.canister);
+ const nftName = `${collection?.name || ''} #${index}`;
+ const type = useGetType(selectedNFT?.url);
+
+ const actionSheetRef = useRef(null);
+ const [isDownloading, setIsDownloading] = useState(false);
+ const [nftDetails, setNFTDetails] = useState>();
+
+ useLayoutEffect(() => {
+ navigation.setOptions({
+ title: isICNS ? selectedNFT?.name : `#${selectedNFT?.index}`,
+ header: (props: StackHeaderProps) => header(props, showBack),
+ });
+ }, [selectedNFT]);
+
+ useEffect(() => {
+ if (selectedNFT && !isICNS) {
+ getNFTDetails({
+ index,
+ canister: selectedNFT.canister,
+ standard: selectedNFT.standard,
+ })
+ .then(details => setNFTDetails(details))
+ .catch(() => {});
+ }
+ return () => setNFTDetails(undefined);
+ }, [selectedNFT]);
+
+ const handleMore = () => {
+ if (isICNS) {
+ const name = selectedNFT?.name?.split('.')[0];
+ Linking.openURL(`${ICNS_URL}/domains/${name}/detail`);
+ } else {
+ actionSheetRef.current?.open();
+ }
+ };
+
+ const handleSend = () => {
+ navigation.navigate(Routes.SEND, { nft: selectedNFT });
+ };
+
+ const downloadNFT = () => {
+ // TODO: Handle download error and permissons error
+ if (type && selectedNFT?.url) {
+ setIsDownloading(true);
+ downloadFile({
+ filename: `NFT_${deleteWhiteSpaces(nftName)}`,
+ mimeType: type,
+ url: selectedNFT?.url,
+ onFetched: () => setIsDownloading(false),
+ });
+ }
+ };
+
+ const moreOptions = {
+ title: t('nftDetail.moreTitle'),
+ options: [
+ {
+ id: 1,
+ label: t('nftDetail.moreOptions.view'),
+ onPress: () => Linking.openURL(selectedNFT?.url!),
+ icon: Platform.select({ android: ViewIcon }),
+ },
+ {
+ id: 2,
+ label: t('nftDetail.moreOptions.download'),
+ onPress: downloadNFT,
+ icon: Platform.select({ android: DownloadIcon }),
+ },
+ ],
+ };
+
+ return (
+ <>
+
+
+ {selectedNFT && (
+
+ )}
+
+
+
+
+
+
+
+
+
+ {collection && selectedNFT && (
+
+ )}
+ {!!collection?.description && (
+
+ {collection.description}
+
+ )}
+ {!!nftDetails?.metadata?.properties?.length && (
+
+ {nftDetails.metadata.properties.map((prop: any) => (
+
+ ))}
+
+ )}
+
+
+ >
+ );
+}
+
+export default NftDetail;
diff --git a/src/screens/tabs/NFTs/screens/NftDetail/styles.js b/src/screens/tabs/NFTs/screens/NftDetail/styles.ts
similarity index 100%
rename from src/screens/tabs/NFTs/screens/NftDetail/styles.js
rename to src/screens/tabs/NFTs/screens/NftDetail/styles.ts
diff --git a/src/screens/tabs/NFTs/screens/NftList/index.tsx b/src/screens/tabs/NFTs/screens/NftList/index.tsx
new file mode 100644
index 00000000..22b520a6
--- /dev/null
+++ b/src/screens/tabs/NFTs/screens/NftList/index.tsx
@@ -0,0 +1,61 @@
+import { FlashList } from '@shopify/flash-list';
+import React, { useLayoutEffect } from 'react';
+
+import { ModalScreenProps } from '@/interfaces/navigation';
+import { CollectionToken } from '@/interfaces/redux';
+import Routes from '@/navigation/Routes';
+import { useAppSelector } from '@/redux/hooks';
+import NftItem, { ITEM_HEIGHT } from '@/screens/tabs/components/NftItem';
+import { isICNSCanister } from '@/utils/assets';
+
+import styles from './styles';
+
+function NftList({ route, navigation }: ModalScreenProps) {
+ const { canisterId } = route.params;
+ const { collections } = useAppSelector(state => state.user);
+ const collection = collections.find(c => c.canisterId === canisterId);
+
+ useLayoutEffect(() => {
+ navigation.setOptions({
+ title: `${collection?.name} (${collection?.tokens.length})`,
+ });
+ }, [collection]);
+
+ const handleItemPress = (item: CollectionToken) => {
+ navigation.navigate(Routes.NFT_DETAIL, {
+ index: item.index,
+ canisterId: item.canister,
+ showBack: true,
+ });
+ };
+
+ const renderItem = ({ item }: { item: CollectionToken }) => {
+ const isICNS = isICNSCanister(collection?.canisterId);
+ const title = isICNS ? item?.name : `${collection?.name} #${item.index}`;
+
+ return (
+ handleItemPress(item)}
+ containerStyle={styles.itemContainer}
+ canisterId={item.canister}
+ itemId={item.index}
+ />
+ );
+ };
+
+ return (
+
+ );
+}
+
+export default NftList;
diff --git a/src/screens/tabs/NFTs/screens/NftList/styles.ts b/src/screens/tabs/NFTs/screens/NftList/styles.ts
new file mode 100644
index 00000000..92819078
--- /dev/null
+++ b/src/screens/tabs/NFTs/screens/NftList/styles.ts
@@ -0,0 +1,8 @@
+import { StyleSheet } from 'react-native';
+
+export default StyleSheet.create({
+ itemContainer: {
+ marginVertical: 10,
+ marginHorizontal: 15,
+ },
+});
diff --git a/src/screens/tabs/NFTs/styles.js b/src/screens/tabs/NFTs/styles.ts
similarity index 59%
rename from src/screens/tabs/NFTs/styles.js
rename to src/screens/tabs/NFTs/styles.ts
index 67efe977..b5e1181f 100644
--- a/src/screens/tabs/NFTs/styles.js
+++ b/src/screens/tabs/NFTs/styles.ts
@@ -1,7 +1,7 @@
import { StyleSheet } from 'react-native';
import { WINDOW_WIDTH } from '@/constants/platform';
-import { FontStyles } from '@/constants/theme';
+import { Colors, FontStyles } from '@/constants/theme';
const itemSize = WINDOW_WIDTH / 2 - 40;
@@ -13,20 +13,25 @@ const commonContainerSize = {
export default StyleSheet.create({
container: {
flex: 1,
- paddingHorizontal: 18,
+ paddingHorizontal: 10,
},
nftsContainer: {
- paddingVertical: 20,
+ paddingBottom: 60,
},
text: {
...FontStyles.SmallGray,
marginTop: 10,
maxWidth: itemSize,
},
- title: {
- paddingLeft: 20,
+ titleContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ paddingHorizontal: 20,
paddingBottom: 20,
- ...FontStyles.Title,
+ },
+ title: FontStyles.Title,
+ totalItems: {
+ color: Colors.White.Secondary,
},
nftDisplayer: {
...commonContainerSize,
@@ -40,4 +45,11 @@ export default StyleSheet.create({
emptyState: {
marginTop: 60,
},
+ itemContainer: {
+ margin: 10,
+ },
+ icnsImage: {
+ backgroundColor: Colors.White.Pure,
+ padding: 20,
+ },
});
diff --git a/src/screens/tabs/Profile/index.js b/src/screens/tabs/Profile/index.tsx
similarity index 70%
rename from src/screens/tabs/Profile/index.js
rename to src/screens/tabs/Profile/index.tsx
index beebb26a..ba8f84ca 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 { RootScreenProps } 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 }: RootScreenProps) {
+ 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 (
<>
@@ -56,7 +75,7 @@ const Profile = () => {
left={
navigation.navigate(Routes.SETTINGS_STACK)}>
+ onPress={() => navigation.navigate(Routes.MODAL_STACK)}>
}
@@ -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/AddICNS/index.tsx b/src/screens/tabs/Profile/modals/Accounts/AddICNS/index.tsx
index 12724b0f..364ba0c9 100644
--- a/src/screens/tabs/Profile/modals/Accounts/AddICNS/index.tsx
+++ b/src/screens/tabs/Profile/modals/Accounts/AddICNS/index.tsx
@@ -135,9 +135,7 @@ function AddICNS({ modalRef }: Props) {
? t('accounts.icns.emptyState')
: t('accounts.icns.info')}
- {noNames
- ? t('accounts.icns.buyICNS')
- : t('accounts.icns.learnMore')}
+ {noNames ? t('accounts.icns.buyICNS') : t('common.learnMore')}
{noNames && t('accounts.icns.proceed')}
diff --git a/src/screens/tabs/Profile/modals/Accounts/index.tsx b/src/screens/tabs/Profile/modals/Accounts/index.tsx
index fd3d9e43..036ed1cf 100644
--- a/src/screens/tabs/Profile/modals/Accounts/index.tsx
+++ b/src/screens/tabs/Profile/modals/Accounts/index.tsx
@@ -1,30 +1,31 @@
-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';
import { Modalize } from 'react-native-modalize';
import {
+ AccountShowcase,
ActionSheet,
- CommonItem,
Header,
Modal,
Text,
Touchable,
} from '@/components/common';
import Icon from '@/components/icons';
+import { COMMON_HITSLOP } from '@/constants/general';
import { FontStyles } from '@/constants/theme';
import CopyIcon from '@/icons/material/Copy.svg';
import EditIcon from '@/icons/material/Edit.svg';
import TrashCan from '@/icons/material/TrashCan.svg';
import AddGray from '@/icons/svg/AddGray.svg';
import CheckedBlueCircle from '@/icons/svg/CheckedBlueCircle.svg';
-import { Nullable } from '@/interfaces/general';
import { Wallet } from '@/interfaces/redux';
import { Row } from '@/layout';
import { useAppDispatch, useAppSelector } from '@/redux/hooks';
import { getICPPrice } from '@/redux/slices/icp';
import { removeAccount, setCurrentPrincipal } from '@/redux/slices/keyring';
+import animationScales from '@/utils/animationScales';
import shortAddress from '@/utils/shortAddress';
import CreateEditAccount from '../CreateEditAccount';
@@ -42,9 +43,8 @@ function Accounts({ modalRef }: Props) {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState(false);
- const [actionSheetData, setActionSheetData] = useState(undefined);
- const [selectedAccount, setSelectedAccount] =
- useState>(null);
+ const [actionSheetData, setActionSheetData] = useState();
+ const [selectedAccount, setSelectedAccount] = useState();
const addICNSRef = useRef(null);
const actionSheetRef = useRef(null);
@@ -58,7 +58,7 @@ function Accounts({ modalRef }: Props) {
}, []);
const onCreateImportAccount = () => {
- setSelectedAccount(null);
+ setSelectedAccount(undefined);
createImportAccountRef.current?.open();
};
@@ -98,7 +98,8 @@ function Accounts({ modalRef }: Props) {
.then(() => {
setLoading(false);
modalRef.current?.close();
- });
+ })
+ .catch(() => {});
};
const onAddICNS = (account: Wallet) => {
@@ -106,7 +107,7 @@ function Accounts({ modalRef }: Props) {
addICNSRef.current?.open();
};
- const onLongPress = (account: Wallet) => {
+ const openAccountOptions = (account: Wallet) => {
const isSelectedAccount = currentWallet?.principal === account.principal;
const isImportedAccount = !account.type.includes('MNEMONIC');
@@ -158,10 +159,16 @@ function Accounts({ modalRef }: Props) {
};
const renderAccountItem = (account: Wallet, index: number) => {
- const isSelectedAccount = currentWallet?.principal === account.principal;
+ const isSelectedAccount =
+ currentWallet?.principal === account.principal &&
+ currentWallet?.type === account.type;
+
+ const handleOpenAccountOptions = () => openAccountOptions(account);
+
const selectedAccountProps = {
- nameStyle: styles.selectedAccount,
- right: ,
+ selected: true,
+ titleStyle: styles.selectedAccount,
+ titleRight: ,
};
const handleOnPress = () => {
@@ -171,24 +178,45 @@ function Accounts({ modalRef }: Props) {
};
return (
- onLongPress(account)}
- onActionPress={() => onLongPress(account)}
+ selected={isSelectedAccount}
+ onLongPress={handleOpenAccountOptions}
+ subtitle={shortAddress(account.principal)}
+ title={account?.icnsData?.reverseResolvedName || account.name}
+ right={
+
+
+
+
+
+ }
{...(isSelectedAccount && selectedAccountProps)}
/>
);
};
+ const resetState = () => {
+ setSelectedAccount(undefined);
+ setActionSheetData(undefined);
+ };
+
return (
<>
-
- {t('accounts.title')}} />
+ {t('accounts.title')}}
+ />
+ }>
{loading && (
@@ -212,13 +240,9 @@ function Accounts({ modalRef }: Props) {
-
+
;
+ 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 handleOnCopy = (address: string) => () => {
+ Clipboard.setString(address);
+ showInfo(t('activity.details.copied'));
+ };
+
+ const ROWS =
+ activity && currentWallet
+ ? [
+ {
+ title: t('activity.details.trxType'),
+ value: getTransactionDetailType(activity.type),
+ },
+ {
+ 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 (
+ {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/modals/CreateEditAccount/index.tsx b/src/screens/tabs/Profile/modals/CreateEditAccount/index.tsx
index 2a049d0c..33d99ea1 100644
--- a/src/screens/tabs/Profile/modals/CreateEditAccount/index.tsx
+++ b/src/screens/tabs/Profile/modals/CreateEditAccount/index.tsx
@@ -25,7 +25,6 @@ import styles from './styles';
interface Props {
modalRef: RefObject;
- accountsModalRef?: RefObject;
account?: Wallet;
pem?: string;
createImportModalRef?: RefObject;
@@ -34,7 +33,6 @@ interface Props {
const CreateEditAccount = ({
modalRef,
account,
- accountsModalRef,
pem,
createImportModalRef,
}: Props) => {
@@ -78,7 +76,7 @@ const CreateEditAccount = ({
if (createImportModalRef) {
createImportModalRef.current?.close();
}
- modalRef.current?.close();
+ handleBack();
};
const resetState = () => {
@@ -101,27 +99,23 @@ const CreateEditAccount = ({
}
}, [account]);
- const closeModal = () => {
- accountsModalRef?.current?.close();
- };
-
const handleBack = () => {
+ Keyboard.dismiss();
modalRef?.current?.close();
};
- const handleClose = () => {
- if (!account) {
- resetState();
- }
- };
-
return (
-
- }
- left={}
- center={{title}}
- />
+ }
+ center={{title}}
+ />
+ }>
;
- accountsModalRef: RefObject;
}
-function CreateImportAccount({ accountsModalRef, modalRef }: Props) {
+function CreateImportAccount({ modalRef }: Props) {
const [pemFile, setPemFile] = useStateWithCallback('');
const createAccountRef = useRef(null);
const importKeyRef = useRef(null);
-
- const closeModal = () => {
- accountsModalRef?.current?.close();
- };
-
- const handleBack = () => {
- modalRef?.current?.close();
- };
+ const toast = useCustomToast();
+ const dispatch = useAppDispatch();
const openCreateAccountModal = () => {
createAccountRef?.current?.open();
@@ -50,10 +41,26 @@ function CreateImportAccount({ accountsModalRef, modalRef }: Props) {
const type = isIos
? 'public.x509-certificate'
: ['.pem', 'application/x-pem-file'];
- const res = await DocumentPicker.pickSingle({ type });
-
- const stringifyPEM = await FileSystem.readFile(res.uri);
- setPemFile(stringifyPEM, openCreateAccountModal);
+ DocumentPicker.pickSingle({ type })
+ .then(async res => {
+ const stringifyPEM = await FileSystem.readFile(res.uri);
+ dispatch(
+ validatePem({
+ pem: stringifyPEM,
+ onSuccess: () => {
+ setPemFile(stringifyPEM, openCreateAccountModal);
+ },
+ onFailure: (eType: string) => {
+ toast.showError(
+ t('accounts.errorImport.title'),
+ getPemImportError(eType)
+ );
+ modalRef.current?.close();
+ },
+ })
+ );
+ })
+ .catch(() => {});
};
const renderButton = ({ id, title, onPress, icon, colors }: Button) => (
@@ -67,31 +74,33 @@ function CreateImportAccount({ accountsModalRef, modalRef }: Props) {
const buttons = getCreateImportButtons({
openCreateAccountModal,
- openFile,
openImportKeyModal,
+ openFile,
});
+ const resetState = () => {
+ setPemFile('');
+ };
+
return (
-
- }
- left={}
- center={
- {t('accounts.createImportAccount')}
- }
- />
+ {t('accounts.createImportAccount')}
+ }
+ />
+ }>
{buttons.map(renderButton)}
-
+
);
}
diff --git a/src/screens/tabs/Profile/modals/CreateImportAccount/utils.ts b/src/screens/tabs/Profile/modals/CreateImportAccount/utils.ts
index 491bca99..2c2ee409 100644
--- a/src/screens/tabs/Profile/modals/CreateImportAccount/utils.ts
+++ b/src/screens/tabs/Profile/modals/CreateImportAccount/utils.ts
@@ -15,12 +15,12 @@ export interface Button {
export const getCreateImportButtons = ({
openCreateAccountModal,
- openFile,
openImportKeyModal,
+ openFile,
}: {
openCreateAccountModal: () => void;
- openFile: () => void;
openImportKeyModal: () => void;
+ openFile: () => void;
}) =>
[
{
diff --git a/src/screens/tabs/Profile/modals/ExportPem/index.tsx b/src/screens/tabs/Profile/modals/ExportPem/index.tsx
index 6e5d2608..a7317e3d 100644
--- a/src/screens/tabs/Profile/modals/ExportPem/index.tsx
+++ b/src/screens/tabs/Profile/modals/ExportPem/index.tsx
@@ -7,6 +7,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import RainbowButton from '@/components/buttons/RainbowButton';
import {
+ AccountShowcase,
ActionButton,
CustomCheckbox,
Header,
@@ -19,9 +20,9 @@ import useCustomToast from '@/hooks/useCustomToast';
import { Wallet } from '@/interfaces/redux';
import { useAppDispatch, useAppSelector } from '@/redux/hooks';
import { getPemFile, validatePassword } from '@/redux/slices/keyring';
+import { requestStoragePermissions } from '@/utils/filesystem';
import shortAddress from '@/utils/shortAddress';
-import AccountShowcase from './components/AccountShowcase';
import styles from './styles';
interface Props {
@@ -64,7 +65,7 @@ function ExportPem({ modalRef }: Props) {
);
};
- const handleExportPem = () => {
+ const downloadPem = () => {
dispatch(
getPemFile({
walletId: selectedWallet.walletId,
@@ -111,15 +112,38 @@ function ExportPem({ modalRef }: Props) {
);
};
+ const handleExportPem = async () => {
+ if (isIos) {
+ downloadPem();
+ } else {
+ await requestStoragePermissions(() => {
+ toast.showError(
+ t('exportPem.permissionError.title'),
+ t('exportPem.permissionError.message')
+ );
+ }, downloadPem);
+ }
+ };
+
const renderAccount = (account: Wallet) => {
const { name, walletId, icon, principal } = account;
+ const selected = selectedWallet.walletId === walletId;
+ const handleSetAccount = () => setSelectedWallet(account);
+
return (
+ }
subtitle={shortAddress(principal)}
- selected={selectedWallet.walletId === walletId}
- onPress={() => setSelectedWallet(account)}
+ selected={selected}
+ onPress={handleSetAccount}
title={account?.icnsData?.reverseResolvedName || name}
/>
);
diff --git a/src/screens/tabs/Profile/modals/ImportKey/index.tsx b/src/screens/tabs/Profile/modals/ImportKey/index.tsx
index 791f9b68..d5c0a248 100644
--- a/src/screens/tabs/Profile/modals/ImportKey/index.tsx
+++ b/src/screens/tabs/Profile/modals/ImportKey/index.tsx
@@ -15,25 +15,16 @@ import { Nullable } from '@/interfaces/general';
import { useAppDispatch } from '@/redux/hooks';
import { validatePem } from '@/redux/slices/keyring';
+import { getPemImportError } from '../../utils';
import CreateEditAccount from '../CreateEditAccount';
import styles from './styles';
interface Props {
modalRef: RefObject;
createImportRef: RefObject;
- accountsModalRef: RefObject;
}
-const getErrorMessage = (errorType: string) => {
- switch (errorType) {
- case 'invalid-key':
- return t('createImportAccount.invalidKey');
- default:
- return t('createImportAccount.addedAccount');
- }
-};
-
-function ImportKey({ createImportRef, modalRef, accountsModalRef }: Props) {
+function ImportKey({ createImportRef, modalRef }: Props) {
const dispatch = useAppDispatch();
const createEditAccount = useRef(null);
@@ -50,11 +41,6 @@ function ImportKey({ createImportRef, modalRef, accountsModalRef }: Props) {
setKey(value);
};
- const closeModal = () => {
- createImportRef?.current?.close();
- accountsModalRef?.current?.close();
- };
-
const handleBack = () => {
modalRef?.current?.close();
};
@@ -77,12 +63,15 @@ function ImportKey({ createImportRef, modalRef, accountsModalRef }: Props) {
};
return (
-
- }
- left={}
- center={{t('common.importWallet')}}
- />
+ }
+ center={{t('common.importWallet')}}
+ />
+ }>
{errorType && (
- {getErrorMessage(errorType)}
+ {getPemImportError(errorType)}
)}
diff --git a/src/screens/tabs/Profile/modals/ImportKey/styles.ts b/src/screens/tabs/Profile/modals/ImportKey/styles.ts
index 9972aef0..4fa446f7 100644
--- a/src/screens/tabs/Profile/modals/ImportKey/styles.ts
+++ b/src/screens/tabs/Profile/modals/ImportKey/styles.ts
@@ -6,7 +6,8 @@ export default StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 24,
- paddingVertical: 32,
+ paddingBottom: 32,
+ paddingTop: 8,
},
inputStyle: {
width: '100%',
diff --git a/src/screens/tabs/Profile/screens/ApprovedCanisters/index.tsx b/src/screens/tabs/Profile/screens/ApprovedCanisters/index.tsx
index d177818c..4a0bdfcb 100644
--- a/src/screens/tabs/Profile/screens/ApprovedCanisters/index.tsx
+++ b/src/screens/tabs/Profile/screens/ApprovedCanisters/index.tsx
@@ -4,14 +4,16 @@ import { ScrollView } from 'react-native-gesture-handler';
import { CommonItem } from '@/components/common';
import { icScanUrl } from '@/constants/urls';
-import { ScreenProps } from '@/interfaces/navigation';
+import { ModalScreenProps } from '@/interfaces/navigation';
import { WCWhiteListItem } from '@/interfaces/walletConnect';
import Routes from '@/navigation/Routes';
import { formatLongDate } from '@/utils/dates';
import styles from './styles';
-function ApprovedCanisters({ route }: ScreenProps) {
+function ApprovedCanisters({
+ route,
+}: ModalScreenProps) {
const { name, canisterList, imageUri, lastConnection } =
route.params.app || {};
diff --git a/src/screens/tabs/Profile/screens/Contacts/index.tsx b/src/screens/tabs/Profile/screens/Contacts/index.tsx
index af14d870..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';
@@ -7,6 +7,7 @@ import { Modalize } from 'react-native-modalize';
import CommonItem from '@/commonComponents/CommonItem';
import Touchable from '@/commonComponents/Touchable';
+import { EmptyState } from '@/components/common';
import ActionSheet, { Option } from '@/components/common/ActionSheet';
import Text from '@/components/common/Text';
import Icon from '@/components/icons';
@@ -32,8 +33,9 @@ function Contacts() {
const addEditContactRef = useRef(null);
const actionSheetRef = useRef(null);
const [actionSheetData, setActionSheetData] = useState();
- const { contacts, contactsLoading } = useAppSelector(state => state.user);
+ const { contactsLoading, contacts } = useAppSelector(state => state.user);
const dispatch = useAppDispatch();
+ const showEmptyState = contacts.length === 0;
const groupedContacts = useMemo(
() =>
@@ -84,14 +86,17 @@ function Contacts() {
return (
<>
-
-
-
- {t('contacts.addContact')}
-
-
+ {!showEmptyState && (
+
+
+
+ {t('contacts.addContact')}
+
+
+ )}
}>
- {groupedContacts.map(section => (
-
- {section.letter}
- {section.contacts.map(contact => {
- const isICNS = validateICNSName(contact.id);
- return (
-
- onPress(contact)}
- onActionPress={() => onPress(contact)}
- disabled={contactsLoading}
- />
-
- );
- })}
-
- ))}
+ {showEmptyState ? (
+
+ ) : (
+ groupedContacts.map(section => (
+
+ {section.letter}
+ {section.contacts.map(contact => {
+ const isICNS = validateICNSName(contact.id);
+ return (
+
+ onPress(contact)}
+ onActionPress={() => onPress(contact)}
+ disabled={contactsLoading}
+ />
+
+ );
+ })}
+
+ ))
+ )}
) {
+function Settings({ navigation }: ModalScreenProps) {
const { biometricsAvailable } = useAppSelector(state => state.user);
const dispatch = useAppDispatch();
@@ -53,7 +53,7 @@ function Settings({ navigation }: ScreenProps) {
deleteWalletRef.current?.close();
navigation.reset({
index: 0,
- routes: [{ name: Routes.WELCOME_SCREEN }],
+ routes: [{ name: Routes.WELCOME }],
});
};
diff --git a/src/screens/tabs/Profile/utils.ts b/src/screens/tabs/Profile/utils.ts
new file mode 100644
index 00000000..e3c90ac0
--- /dev/null
+++ b/src/screens/tabs/Profile/utils.ts
@@ -0,0 +1,12 @@
+import { t } from 'i18next';
+
+export const getPemImportError = (error: string) => {
+ switch (error) {
+ case 'invalid-key':
+ return t('createImportAccount.invalidKey');
+ case 'added-account':
+ return t('createImportAccount.alreadyImported');
+ default:
+ return t('createImportAccount.importError');
+ }
+};
diff --git a/src/screens/tabs/Tokens/components/AddToken/index.tsx b/src/screens/tabs/Tokens/components/AddToken/index.tsx
index f8e47ed5..07b15d68 100644
--- a/src/screens/tabs/Tokens/components/AddToken/index.tsx
+++ b/src/screens/tabs/Tokens/components/AddToken/index.tsx
@@ -1,15 +1,18 @@
+import { t } from 'i18next';
import React, { useCallback, useRef } from 'react';
-import { Image } from 'react-native';
import { Modalize } from 'react-native-modalize';
-import { Modal, Touchable } from '@/components/common';
-import animationScales from '@/utils/animationScales';
+import ScrollableButton from '@/components/buttons/ScrollableButton';
+import { Modal } from '@/components/common';
-import Add from './assets/Add.png';
import useSteps from './hooks/useSteps';
import styles from './styles';
-export function AddToken() {
+interface Props {
+ scrollPosition: number;
+}
+
+export function AddToken({ scrollPosition }: Props) {
const modalRef = useRef(null);
const handleModalClose = useCallback(() => {
@@ -20,12 +23,13 @@ export function AddToken() {
return (
<>
- modalRef?.current?.open()}
- scale={animationScales.medium}
- style={styles.buttonContainer}>
-
-
+ buttonStyle={styles.buttonContainer}
+ />
- {t('addToken.learnMore')}
+ {t('common.learnMore')}
)}
diff --git a/src/screens/tabs/Tokens/components/AddToken/styles.ts b/src/screens/tabs/Tokens/components/AddToken/styles.ts
index 37112e64..41b92be2 100644
--- a/src/screens/tabs/Tokens/components/AddToken/styles.ts
+++ b/src/screens/tabs/Tokens/components/AddToken/styles.ts
@@ -6,7 +6,7 @@ export default StyleSheet.create({
buttonContainer: {
position: 'absolute',
bottom: 16,
- right: 20,
+ right: 14,
borderRadius: 100,
},
title: {
diff --git a/src/screens/tabs/Tokens/index.js b/src/screens/tabs/Tokens/index.js
index 3058f42d..d803e11c 100644
--- a/src/screens/tabs/Tokens/index.js
+++ b/src/screens/tabs/Tokens/index.js
@@ -1,9 +1,10 @@
-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';
import { Alert, RefreshControl, ScrollView } from 'react-native';
+import useScrollHanlder from '@/components/buttons/ScrollableButton/hooks/useScrollHandler';
import { ActionSheet, ErrorState, Text } from '@/components/common';
import TokenItem from '@/components/tokens/TokenItem';
import { ERROR_TYPES } from '@/constants/general';
@@ -12,23 +13,23 @@ import CopyIcon from '@/icons/material/Copy.svg';
import DeleteIcon from '@/icons/material/Delete.svg';
import SendIcon from '@/icons/material/Send.svg';
import { Container, Row, Separator } from '@/layout';
+import Routes from '@/navigation/Routes';
import { useAppDispatch, useAppSelector } from '@/redux/hooks';
import { getBalance, removeCustomToken } from '@/redux/slices/user';
-import Send from '@/screens/flows/Send';
import { isDefaultToken } from '@/utils/assets';
import WalletHeader from '../components/WalletHeader';
import { AddToken } from './components/AddToken';
import styles from './styles';
-function Tokens() {
+function Tokens({ navigation }) {
const dispatch = useAppDispatch();
const { assets, assetsLoading, assetsError } = useAppSelector(
state => state.user
);
const [selectedToken, setSelectedToken] = useState(null);
+ const { handleOnScroll, scrollPosition } = useScrollHanlder();
- const sendRef = useRef(null);
const actionsRef = useRef(null);
const listRef = useRef(null);
useScrollToTop(listRef);
@@ -62,7 +63,11 @@ function Tokens() {
{
id: 1,
label: t('tokensTab.tokenActions.send'),
- onPress: sendRef.current?.open,
+ onPress: () =>
+ navigation.navigate(Routes.MODAL_STACK, {
+ screen: Routes.SEND,
+ params: { token: selectedToken },
+ }),
icon: SendIcon,
},
{
@@ -114,10 +119,12 @@ function Tokens() {
{!assetsError ? (
<>
))}
-
+
>
) : (
)}
-
{
- 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/NftItem/index.js b/src/screens/tabs/components/NftItem/index.js
deleted file mode 100644
index 313d520b..00000000
--- a/src/screens/tabs/components/NftItem/index.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React, { useState } from 'react';
-import { View } from 'react-native';
-
-import NftDisplayer from '@/commonComponents/NftDisplayer';
-import Text from '@/commonComponents/Text';
-import Touchable from '@/commonComponents/Touchable';
-import { ICNS_CANISTER_ID } from '@/constants/canister';
-import useGetType from '@/hooks/useGetType';
-
-import styles, { ITEM_SIZE } from './styles';
-export const ITEM_HEIGHT = ITEM_SIZE;
-
-function NftItem({ item, onOpen }) {
- const { url, canister, index, name, collectionName } = item;
- const isICNS = canister === ICNS_CANISTER_ID;
- const title = isICNS ? collectionName : `${collectionName} #${index}`;
- const [type, setType] = useState(null);
-
- useGetType(url, setType);
-
- const handleOnPress = () => {
- onOpen({ ...item, type })();
- };
-
- return (
-
-
-
-
-
- {title}
-
-
- );
-}
-
-export default NftItem;
diff --git a/src/screens/tabs/components/NftItem/index.tsx b/src/screens/tabs/components/NftItem/index.tsx
new file mode 100644
index 00000000..9a45f93c
--- /dev/null
+++ b/src/screens/tabs/components/NftItem/index.tsx
@@ -0,0 +1,71 @@
+import React from 'react';
+import { StyleProp, TextStyle, ViewStyle } from 'react-native';
+
+import { NftDisplayer, Text, Touchable } from '@/components/common';
+import useGetType from '@/hooks/useGetType';
+
+import styles, { CONTAINER_HEIGHT } from './styles';
+
+export const ITEM_HEIGHT = CONTAINER_HEIGHT;
+
+interface Props {
+ url: string;
+ onPress: () => void;
+ canisterId: string;
+ itemId: string | number;
+ titleStyle?: StyleProp;
+ containerStyle?: StyleProp;
+ itemStyle?: StyleProp;
+ title?: string;
+ subtitle?: string;
+ ICNSName?: string;
+}
+
+function NftItem({
+ onPress,
+ url,
+ title,
+ titleStyle,
+ subtitle,
+ containerStyle,
+ itemStyle,
+ canisterId,
+ itemId,
+ ICNSName,
+}: Props) {
+ const type = useGetType(url);
+ return (
+
+
+ {title && (
+
+ {title}
+
+ )}
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+ );
+}
+
+export default NftItem;
diff --git a/src/screens/tabs/components/NftItem/styles.js b/src/screens/tabs/components/NftItem/styles.js
deleted file mode 100644
index 1dba802b..00000000
--- a/src/screens/tabs/components/NftItem/styles.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { StyleSheet } from 'react-native';
-
-import { WINDOW_WIDTH } from '@/constants/platform';
-import { FontStyles } from '@/constants/theme';
-
-export const ITEM_SIZE = WINDOW_WIDTH / 2 - 40;
-
-const commonContainerSize = {
- width: ITEM_SIZE,
- height: ITEM_SIZE,
-};
-
-export default StyleSheet.create({
- text: {
- ...FontStyles.SmallGray,
- marginTop: 10,
- maxWidth: ITEM_SIZE,
- },
- item: {
- margin: 10,
- },
- title: {
- paddingLeft: 20,
- paddingBottom: 20,
- ...FontStyles.Title,
- },
- nftDisplayer: {
- ...commonContainerSize,
- },
- touchable: {
- ...commonContainerSize,
- },
-});
diff --git a/src/screens/tabs/components/NftItem/styles.ts b/src/screens/tabs/components/NftItem/styles.ts
new file mode 100644
index 00000000..30743097
--- /dev/null
+++ b/src/screens/tabs/components/NftItem/styles.ts
@@ -0,0 +1,29 @@
+import { StyleSheet } from 'react-native';
+
+import { WINDOW_WIDTH } from '@/constants/platform';
+import { Colors } from '@/constants/theme';
+
+const LOGO_SIZE = WINDOW_WIDTH / 2 - 30;
+
+// This is estimated if item has title and subtitle
+export const CONTAINER_HEIGHT = LOGO_SIZE + 50;
+
+export default StyleSheet.create({
+ display: {
+ width: LOGO_SIZE,
+ height: LOGO_SIZE,
+ backgroundColor: Colors.Black.Pure,
+ borderRadius: 22,
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ justifyContent: 'center',
+ alignContent: 'center',
+ marginBottom: 8,
+ },
+ title: {
+ color: Colors.White.Pure,
+ },
+ subtitle: {
+ color: Colors.Gray.Pure,
+ },
+});
diff --git a/src/screens/tabs/components/WalletHeader/index.js b/src/screens/tabs/components/WalletHeader/index.js
index efca5a52..40c79d8c 100644
--- a/src/screens/tabs/components/WalletHeader/index.js
+++ b/src/screens/tabs/components/WalletHeader/index.js
@@ -13,7 +13,6 @@ import { useDebounce } from '@/hooks/useDebounce';
import Routes from '@/navigation/Routes';
import { useAppSelector } from '@/redux/hooks';
import Deposit from '@/screens/flows/Deposit';
-import Send from '@/screens/flows/Send';
import ActionButton from '../ActionButton';
import DepositIcon from './assets/Deposit.png';
@@ -25,7 +24,6 @@ import styles from './styles';
const WalletHeader = () => {
const { t } = useTranslation();
const modalRef = useRef(null);
- const sendRef = useRef(null);
const depositRef = useRef(null);
const { debounce } = useDebounce();
const [navigated, setNavigated] = useState(false);
@@ -39,7 +37,7 @@ const WalletHeader = () => {
const openSend = () => {
modalRef.current?.close();
- sendRef.current?.open();
+ navigation.navigate(Routes.MODAL_STACK, { screen: Routes.SEND });
};
const openDeposit = () => {
@@ -79,7 +77,7 @@ const WalletHeader = () => {
const handleGoToProfile = () => {
if (!navigated) {
setNavigated(true);
- navigation.jumpTo(Routes.PROFILE_SCREEN);
+ navigation.jumpTo(Routes.PROFILE);
}
};
@@ -111,7 +109,6 @@ const WalletHeader = () => {
-
>
);
diff --git a/src/screens/tabs/components/utils.js b/src/screens/tabs/components/utils.js
index 48531a02..2fd47eaa 100644
--- a/src/screens/tabs/components/utils.js
+++ b/src/screens/tabs/components/utils.js
@@ -2,39 +2,73 @@ 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 getTypeIcon = type => {
+ switch (type) {
+ case ACTIVITY_TYPES.RECEIVE:
+ return ACTIVITY_IMAGES.RECEIVE;
+ case ACTIVITY_TYPES.BURN:
+ return ACTIVITY_IMAGES.BURN;
+ case ACTIVITY_TYPES.SEND:
+ case ACTIVITY_TYPES.TRANSFERFROM:
+ return ACTIVITY_IMAGES.SEND;
+ case ACTIVITY_TYPES.MINT:
+ return ACTIVITY_IMAGES.MINT;
+ default:
+ return 'actionActivity';
+ }
+};
-export const getTitle = (type, symbol, swapData, plug) => {
+export const getTransactionDetailType = type => {
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':
+ case ACTIVITY_TYPES.RECEIVE:
+ case ACTIVITY_TYPES.SEND:
+ case ACTIVITY_TYPES.TRANSFERFROM:
+ return t('transactionTypes.transfer');
+ default:
+ return `${capitalize(type?.toLowerCase())}`;
+ }
+};
+
+export const getTitle = (type, symbol) => {
+ switch (type) {
+ case ACTIVITY_TYPES.DIRECT_BUY:
return t('transactionTypes.buyNTF');
- case 'MAKELISTING':
+ case ACTIVITY_TYPES.MAKE_LISTING:
return t('transactionTypes.listNFT');
- case 'CANCELLISTING':
+ case ACTIVITY_TYPES.CANCEL_LISTING:
return t('transactionTypes.cancelListingNFT');
- case 'MAKEOFFER':
+ case ACTIVITY_TYPES.MAKE_OFFER:
return t('transactionTypes.makeOfferNFT');
- case 'ACCEPTOFFER':
+ case ACTIVITY_TYPES.ACCEPT_OFFER:
return t('transactionTypes.acceptOfferNFT');
- case 'CANCELOFFER':
+ case ACTIVITY_TYPES.CANCEL_OFFER:
return t('transactionTypes.cancelOfferNFT');
- case 'DENYOFFER':
+ case ACTIVITY_TYPES.DENY_OFFER:
return t('transactionTypes.denyOfferNFT');
+ case ACTIVITY_TYPES.TRANSFERFROM:
+ return `${t('transactionTypes.send')} ${symbol ?? ''}`;
default:
if (type.includes('Liquidity')) {
return type;
@@ -63,21 +97,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/services/DAB.ts b/src/services/DAB.ts
index e90aff56..fcdefc73 100644
--- a/src/services/DAB.ts
+++ b/src/services/DAB.ts
@@ -12,7 +12,6 @@ import { fetch } from 'react-native-fetch-api';
import { DFINITY_HOST, IC_URL_HOST } from '@/constants/urls';
import { DABToken } from '@/interfaces/dab';
-import { CollectionToken } from '@/interfaces/redux';
import { recursiveParseBigint, recursiveParsePrincipal } from '@/utils/objects';
export const getDabTokens = async (): Promise => {
@@ -43,7 +42,11 @@ export const getNFTDetails = async ({
index,
canister,
standard,
-}: CollectionToken): Promise> => {
+}: {
+ index: number | string;
+ canister: string;
+ standard: string;
+}): Promise> => {
const agent = new HttpAgent({ fetch, host: IC_URL_HOST });
const NFTActor = getNFTActor({ canisterId: canister, standard, agent });
const details = await NFTActor.details(index);
diff --git a/src/translations/en/index.js b/src/translations/en/index.js
index ead16c9e..55363c75 100644
--- a/src/translations/en/index.js
+++ b/src/translations/en/index.js
@@ -32,14 +32,16 @@ const translations = {
the: 'The',
enterPassword: 'Enter Password',
importWallet: 'Import Wallet',
+ learnMore: 'Learn More',
},
routes: {
[Routes.NFTS]: 'Collectibles',
[Routes.TOKENS]: 'Tokens',
- [Routes.PROFILE_SCREEN]: 'Profile',
+ [Routes.PROFILE]: 'Profile',
[Routes.SETTINGS]: 'Settings',
[Routes.CONTACTS]: 'Contacts',
[Routes.APPROVED_CANISTERS]: 'Approved Canisters',
+ [Routes.SEND]: 'Send',
},
welcome: {
title: 'Welcome to Plug',
@@ -97,6 +99,8 @@ const translations = {
acceptOfferNFT: 'Accept Offer',
cancelOfferNFT: 'Cancel Offer',
denyOfferNFT: 'Deny Offer',
+ transfer: 'Transfer',
+ send: 'Send',
},
reviewSend: {
to: 'To',
@@ -127,18 +131,29 @@ const translations = {
emptyTitle: "You don't own any Collectibles yet",
emptySubtitle:
"When you do, they'll show here, where you will see their traits and send them.",
+ items: '{{count}} item',
+ items_plural: '{{count}} items',
},
nftDetail: {
collectionTitle: '🧩 Collection',
descriptionTitle: '📝 Description',
attributesTitle: '🎛 Attributes',
moreTitle: 'More Options',
+ manage: 'Manage',
moreOptions: {
view: 'View',
download: 'Download',
},
},
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',
@@ -190,7 +205,6 @@ const translations = {
icns: {
setICNS: 'Choose ICNS Name',
none: 'None',
- learnMore: 'Learn More',
emptyState:
'We weren’t able to find any ICNS names in your Plug account. ',
buyICNS: 'Buy an ICNS name',
@@ -215,6 +229,11 @@ const translations = {
title: 'Contacts',
addContact: 'Add contact',
editContact: 'Edit contact',
+ emptyState: {
+ title: 'Empty Contact List',
+ message:
+ 'You don’t have any contacts yet. Invite your friends to send tokens, collectibles, and more.',
+ },
moreOptions: {
edit: 'Edit Contact',
copy: 'Copy Address',
@@ -288,7 +307,6 @@ const translations = {
'Invalid token’s standard. Select a valid option for the current canister ID.',
invalidCanisterTokenError: 'Invalid Canister ID.',
nftTokenError: 'Custom non-fungible tokens are not supported yet.',
- learnMore: 'Learn More',
poweredByDab: 'POWERED BY DAB',
},
walletConnect: {
@@ -314,7 +332,6 @@ const translations = {
unsafeDappName: 'Unknown DApp',
unknown: 'Unknown',
unknownArguments: 'Unknown arguments',
- learnMore: 'Learn More',
},
deleteWallet: {
title: 'Delete Wallet',
@@ -329,7 +346,9 @@ const translations = {
importPem: 'PEM File',
create: 'Create',
invalidKey: 'Invalid key. Please, try again.',
- addedAccount: 'Account already added.',
+ alreadyImported: 'This account is already imported',
+ importError:
+ 'There was an error while importing the account. Please try again.',
},
exportPem: {
safeCheck: 'I’ll be safe with my DFX Identity.',
@@ -339,6 +358,10 @@ const translations = {
message:
'There was an error while exporting the account. Please try again.',
},
+ permissionError: {
+ title: 'Error Exporting Account',
+ message: 'You need to allow the app to access storage to save the file.',
+ },
success: {
title: 'Account Successfully Exported',
messageIos: 'Your .pem file should be located at the choosen directory.',
@@ -347,6 +370,29 @@ const translations = {
selectAccount:
"Select the account you would like to export it's DFX Identity.",
},
+ addCollection: {
+ customCollectionId: 'Collection Canister ID',
+ customCollectionStandard: 'Collection Interface Standard',
+ customCollection: 'Custom Collection',
+ customCaption: 'This allows Collectibles to be used by Plug',
+ title: 'Add Collection',
+ noName: 'Unable to load name',
+ invalidCanisterId: 'Invalid canister ID. ',
+ canisterNotCompatible:
+ 'Canister Id not compatible with {{standard}}. Please, try again.',
+ safetyAlert:
+ 'Collection Safety Alert: For your security, make sure to do proper research before interacting with any Collections.',
+ errorToastTitle: 'Error Adding Custom Collection',
+ errorToastMessage:
+ 'There was an unexpected error while trying to add a custom collection. Please try again later.',
+ infoToastTitle: 'Custom Collection Already Added',
+ infoToastMessage:
+ 'You’ve added this collection before. Remember, you need to own items from {{name}} to be able to access it.',
+ successToastTitle: 'Custom Collection Successfully Added',
+ successToastMessage:
+ 'You’ll only be able to access the collection if own items from it.',
+ unknownCollection: 'unknown collection',
+ },
};
export default translations;
diff --git a/src/utils/assets.ts b/src/utils/assets.ts
index e4b28d65..3e41bed5 100644
--- a/src/utils/assets.ts
+++ b/src/utils/assets.ts
@@ -1,5 +1,5 @@
import { TOKENS } from '@/constants/assets';
-import { ICP_CANISTER_ID } from '@/constants/canister';
+import { ICNS_CANISTER_ID, ICP_CANISTER_ID } from '@/constants/canister';
import { Collection, CollectionToken } from '@/interfaces/redux';
import { validateCanisterId } from '@/utils/ids';
@@ -23,7 +23,19 @@ export const getToken = (
export const isDefaultToken = (canisterId: string) =>
!!Object.values(TOKENS).find(token => token.canisterId === canisterId);
-export const formatCollections = (collections: Collection[]) =>
+export interface FormattedCollection {
+ index: string | number;
+ canister: string;
+ url: string;
+ standard: string;
+ metadata: any;
+ collectionDescription: string;
+ collectionName: string;
+}
+
+export const formatCollections = (
+ collections: Collection[]
+): FormattedCollection[] =>
collections.flatMap(collection =>
collection?.tokens.map((token: CollectionToken) => ({
collectionDescription: collection.description,
@@ -31,3 +43,6 @@ export const formatCollections = (collections: Collection[]) =>
...token,
}))
);
+
+export const isICNSCanister = (canisterId?: string) =>
+ canisterId === ICNS_CANISTER_ID;
diff --git a/src/utils/fileTypes.js b/src/utils/fileTypes.js
deleted file mode 100644
index 3fa89655..00000000
--- a/src/utils/fileTypes.js
+++ /dev/null
@@ -1,29 +0,0 @@
-export const FILE_TYPES = {
- SVG: '.svg',
- JPG: '.jpg',
- JPEG: '.jpeg',
- PNG: '.png',
- HTML: '.html',
- MP4: '.mp4',
-};
-
-export const getAbsoluteType = type => {
- if (!type) {
- return '';
- }
-
- switch (true) {
- case type.includes('svg'):
- return FILE_TYPES.SVG;
- case type.includes('jpg'):
- return FILE_TYPES.JPG;
- case type.includes('jpeg'):
- return FILE_TYPES.JPEG;
- case type.includes('png'):
- return FILE_TYPES.PNG;
- case type.includes('html'):
- return FILE_TYPES.HTML;
- case type.includes('mp4'):
- return FILE_TYPES.MP4;
- }
-};
diff --git a/src/utils/fileTypes.ts b/src/utils/fileTypes.ts
new file mode 100644
index 00000000..0c1f5c08
--- /dev/null
+++ b/src/utils/fileTypes.ts
@@ -0,0 +1,22 @@
+import mime from 'mime-types';
+
+/**
+ * Function that returns the mime-type of a given url
+ * @param value URL of file
+ * @returns mime-type of file
+ */
+export const getType = (value?: string | null): string | undefined => {
+ if (!value) {
+ return undefined;
+ }
+
+ return mime.lookup(value);
+};
+
+export const getExtension = (value?: string | null): string | undefined => {
+ if (!value) {
+ return undefined;
+ }
+
+ return mime.extension(value);
+};
diff --git a/src/utils/filesystem.ts b/src/utils/filesystem.ts
index a4c056e0..39ef19d8 100644
--- a/src/utils/filesystem.ts
+++ b/src/utils/filesystem.ts
@@ -4,7 +4,9 @@ import FileViewer from 'react-native-file-viewer';
import { isAndroid, isIos } from '@/constants/platform';
-const requestStoragePermissions = async (
+import { getExtension } from './fileTypes';
+
+export const requestStoragePermissions = async (
onError?: () => void,
onSuccess?: () => void
) => {
@@ -32,14 +34,16 @@ const requestStoragePermissions = async (
interface DownloadFileProps {
filename: string;
url: string;
- onFetched: () => void;
- onSuccess: () => void;
- onError: () => void;
+ mimeType: string;
+ onFetched?: () => void;
+ onSuccess?: () => void;
+ onError?: () => void;
}
export const downloadFile = async ({
filename,
url,
+ mimeType,
onFetched,
onSuccess,
onError,
@@ -49,23 +53,25 @@ export const downloadFile = async ({
await requestStoragePermissions(onError);
}
const dirToSave = Dirs.DocumentDir;
- const path = `${dirToSave}/${filename}`;
+ const extension = getExtension(mimeType);
+ const path = `${dirToSave}/${filename}.${extension}`;
FileSystem.fetch(url, {
method: 'GET',
path,
}).then(async res => {
onFetched?.();
+ const nameWithExtension = `${filename}.${extension}`;
if (isAndroid) {
- await FileSystem.cpExternal(path, filename, 'downloads');
+ await FileSystem.cpExternal(path, nameWithExtension, 'downloads');
}
if (res.ok) {
FileViewer.open(path, {
showOpenWithDialog: true,
- displayName: filename,
+ displayName: nameWithExtension,
}).then(() => {
if (isIos) {
- Share.share({ url: path, title: filename });
+ Share.share({ url: path, title: nameWithExtension });
}
onSuccess?.();
});
diff --git a/src/utils/ids.ts b/src/utils/ids.ts
index c53fbc0e..9782a2d9 100644
--- a/src/utils/ids.ts
+++ b/src/utils/ids.ts
@@ -1,10 +1,12 @@
import { Principal } from '@dfinity/principal';
import {
+ ACCOUNT_ID_LENGTH,
ALPHANUM_REGEX,
CANISTER_MAX_LENGTH,
ICNS_REGEX,
} from '@/constants/addresses';
+import { Wallet } from '@/interfaces/redux';
export const validateICNSName = (name: string) => ICNS_REGEX.test(name);
@@ -17,7 +19,7 @@ export const validatePrincipalId = (text: string) => {
};
export const validateAccountId = (text: string) =>
- text.length === 64 && ALPHANUM_REGEX.test(text);
+ text.length === ACCOUNT_ID_LENGTH && ALPHANUM_REGEX.test(text);
export const validateCanisterId = (text: string) => {
try {
@@ -26,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 7fbb85c4..65233d3c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -96,6 +96,15 @@
json5 "^2.2.1"
semver "^6.3.0"
+"@babel/eslint-parser@^7.18.2":
+ version "7.19.1"
+ resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz#4f68f6b0825489e00a24b41b6a1ae35414ecd2f4"
+ integrity sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==
+ dependencies:
+ "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1"
+ eslint-visitor-keys "^2.1.0"
+ semver "^6.3.0"
+
"@babel/generator@^7.14.0", "@babel/generator@^7.17.3":
version "7.17.3"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.3.tgz#a2c30b0c4f89858cb87050c3ffdfd36bdf443200"
@@ -425,7 +434,7 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
-"@babel/parser@^7.1.0", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.7.0":
+"@babel/parser@^7.1.0", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3":
version "7.17.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.3.tgz#b07702b982990bf6fdc1da5049a23fece4c5c3d0"
integrity sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==
@@ -993,7 +1002,7 @@
"@babel/parser" "^7.16.7"
"@babel/types" "^7.16.7"
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.0", "@babel/traverse@^7.17.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.4":
+"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.0", "@babel/traverse@^7.17.3", "@babel/traverse@^7.7.4":
version "7.17.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57"
integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==
@@ -1041,7 +1050,7 @@
debug "^4.1.0"
globals "^11.1.0"
-"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.7.0":
+"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b"
integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==
@@ -1752,6 +1761,34 @@
resolved "https://registry.yarnpkg.com/@juanelas/base64/-/base64-1.0.5.tgz#abbda3f529947b4d9784619c4fa039a3f9b58399"
integrity sha512-gTIElNo4ohMcYUZzol/Hb6DYJzphxl0b1B4egJJ+JiqxqcOcWx4XLMAB+lhWuMsMX3uR1oc5hwPusU3lgc1FkQ==
+"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
+ version "5.1.1-v1"
+ resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
+ integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==
+ dependencies:
+ eslint-scope "5.1.1"
+
+"@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+"@nodelib/fs.walk@^1.2.3":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
+
"@psychedelic/cap-js@0.0.7":
version "0.0.7"
resolved "https://npm.pkg.github.com/download/@Psychedelic/cap-js/0.0.7/8184d6ab5732f9a69380dfba5aed4d62e5e71051#8184d6ab5732f9a69380dfba5aed4d62e5e71051"
@@ -1763,10 +1800,10 @@
axios "^0.24.0"
cross-fetch "^3.1.4"
-"@psychedelic/dab-js@1.4.12":
- version "1.4.12"
- resolved "https://npm.pkg.github.com/download/@Psychedelic/dab-js/1.4.12/ee252e693f8606327f479cbbdbfe8424b422e8f5#ee252e693f8606327f479cbbdbfe8424b422e8f5"
- integrity sha512-oJwMbgafesO5KOBoV70oQ929wel18n0X6u/IBtR7mcBa0SdwM2uyhbgjNqVLpFdBaVBRoAxKdCABc4sVRcqglg==
+"@psychedelic/dab-js@1.6.0-alpha.2", "@psychedelic/dab-js@^1.6.0-alpha.2":
+ version "1.6.0-alpha.2"
+ resolved "https://npm.pkg.github.com/download/@Psychedelic/dab-js/1.6.0-alpha.2/51407ac0fc322e5ae00014ca2790e2f249de1b65#51407ac0fc322e5ae00014ca2790e2f249de1b65"
+ integrity sha512-4BURtD6BI6XYIR3n5j9A22B/rcgwUmiDL8qVXULROYmz+d8HxQaoDsov24Roo8EWwIK1VNS4LEvp/qKMuLjzvg==
dependencies:
"@dfinity/agent" "0.9.3"
"@dfinity/candid" "0.9.3"
@@ -1777,31 +1814,17 @@
cross-fetch "^3.1.4"
crypto-js "^4.1.1"
-"@psychedelic/dab-js@1.5.0-beta.1":
- version "1.5.0-beta.1"
- resolved "https://npm.pkg.github.com/download/@Psychedelic/dab-js/1.5.0-beta.1/306e2bb10491927995a3a22f25cd2ca9fd40990e#306e2bb10491927995a3a22f25cd2ca9fd40990e"
- integrity sha512-p7GEh++Fv8U1SIYdqjxLLNy3I8fjoEF+lrXsHHKxcFuKpINxWkfWmUxLnjS6rytY+VuovrDEISFD4Tz0QHD2DQ==
- dependencies:
- "@dfinity/agent" "0.9.3"
- "@dfinity/candid" "0.9.3"
- "@dfinity/identity" "0.9.3"
- "@dfinity/principal" "0.9.3"
- axios "^0.24.0"
- buffer-crc32 "^0.2.13"
- cross-fetch "^3.1.4"
- crypto-js "^4.1.1"
-
-"@psychedelic/plug-controller@0.24.5":
- version "0.24.5"
- resolved "https://npm.pkg.github.com/download/@Psychedelic/plug-controller/0.24.5/a0a8f11fc13a81626297f03e25a16b43e6010742#a0a8f11fc13a81626297f03e25a16b43e6010742"
- integrity sha512-du6M8VkVLxVDWiwisRxv941Xy3FJMeLVZF2d09v+GU56eoZ31s9D/Ch5P8TrACoTVAo3UJGHEoMkHFN45ihoHA==
+"@psychedelic/plug-controller@0.25.3":
+ version "0.25.3"
+ resolved "https://npm.pkg.github.com/download/@Psychedelic/plug-controller/0.25.3/107a782e3f950ad25c3c36dcd097aaabe670714c#107a782e3f950ad25c3c36dcd097aaabe670714c"
+ integrity sha512-+wrLuBJpLvJZce11AEdo2j/8gD8w1YJpTmVZxBWmRlIY4pRGGd8/Yu8WDu7rKbRnV5tGgymuhDhOZ6fXV2H0FA==
dependencies:
"@dfinity/agent" "0.9.3"
"@dfinity/candid" "0.9.3"
"@dfinity/identity" "0.9.3"
"@dfinity/principal" "0.9.3"
"@psychedelic/cap-js" "0.0.7"
- "@psychedelic/dab-js" "1.5.0-beta.1"
+ "@psychedelic/dab-js" "1.6.0-alpha.2"
"@types/secp256k1" "^4.0.3"
axios "^0.21.1"
babel-jest "^25.5.1"
@@ -1831,6 +1854,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"
@@ -2021,29 +2049,24 @@
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@^2.0.0":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@react-native-community/eslint-config/-/eslint-config-2.0.0.tgz#35dcc529a274803fc4e0a6b3d6c274551fb91774"
- integrity sha512-vHaMMfvMp9BWCQQ0lNIXibOJTcXIbYUQ8dSUsMOsrXgVkeVQJj88OwrKS00rQyqwMaC4/a6HuDiFzYUkGKOpVg==
+"@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"
+ integrity sha512-LCN0QkMNIHoXp2B/uedxQI2GMLbupkIDKSb/6Q7e+pHp4fHrGIkmixSDR5sbjzeqNIf7a1+VcRxRp9u6qv10Ng==
dependencies:
+ "@babel/core" "^7.14.0"
+ "@babel/eslint-parser" "^7.18.2"
"@react-native-community/eslint-plugin" "^1.1.0"
- "@typescript-eslint/eslint-plugin" "^3.1.0"
- "@typescript-eslint/parser" "^3.1.0"
- babel-eslint "^10.1.0"
- eslint-config-prettier "^6.10.1"
- eslint-plugin-eslint-comments "^3.1.2"
- eslint-plugin-flowtype "2.50.3"
- eslint-plugin-jest "22.4.1"
- eslint-plugin-prettier "3.1.2"
- eslint-plugin-react "^7.20.0"
- eslint-plugin-react-hooks "^4.0.4"
- eslint-plugin-react-native "^3.8.1"
- prettier "^2.0.2"
+ "@typescript-eslint/eslint-plugin" "^5.30.5"
+ "@typescript-eslint/parser" "^5.30.5"
+ eslint-config-prettier "^8.5.0"
+ eslint-plugin-eslint-comments "^3.2.0"
+ eslint-plugin-ft-flow "^2.0.1"
+ eslint-plugin-jest "^26.5.3"
+ eslint-plugin-prettier "^4.2.1"
+ eslint-plugin-react "^7.30.1"
+ eslint-plugin-react-hooks "^4.6.0"
+ eslint-plugin-react-native "^4.0.0"
"@react-native-community/eslint-plugin@^1.1.0":
version "1.1.0"
@@ -2096,15 +2119,15 @@
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-1.3.6.tgz#fa700318528db93f05144b1be4b691b9c1dd1abe"
integrity sha512-pNJ8R9JMga6SXOw6wGVN0tjmE6vegwPmJBL45SEMX2fqTfAk2ykDnlJHodRpHpAgsv0DaI8qX76z3A+aqKSU0w==
-"@react-navigation/material-top-tabs@^6.2.4":
- version "6.2.4"
- resolved "https://registry.yarnpkg.com/@react-navigation/material-top-tabs/-/material-top-tabs-6.2.4.tgz#1db5a1bf32525e1c9dd8aa77cd549366034d4f77"
- integrity sha512-Il9rPA3jXAFTPBzk88sjCbSRHRRrr1e6HFRx3G+ttSr6q5HQkasksQiEe28euouSwv8M7ChEbXnhNWIR+Sroqg==
+"@react-navigation/material-top-tabs@^6.3.0":
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/@react-navigation/material-top-tabs/-/material-top-tabs-6.3.0.tgz#ac49389155a3c1cb7a82119491e2059a7557718e"
+ integrity sha512-nv9c3WbmzuwYZqDkinP375aTYzQIXkCzAmpHRsVH3AjpF2Qc62oKDjWuSMFdRn3VPe3LAH/0P6yUzQSX/800yQ==
dependencies:
color "^4.2.3"
warn-once "^0.1.0"
-"@react-navigation/native@^6.0.8":
+"@react-navigation/native@^6.0.13":
version "6.0.13"
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-6.0.13.tgz#ec504120e193ea6a7f24ffa765a1338be5a3160a"
integrity sha512-CwaJcAGbhv3p3ECablxBkw8QBCGDWXqVRwQ4QbelajNW623m3sNTC9dOF6kjp8au6Rg9B5e0KmeuY0xWbPk79A==
@@ -2121,10 +2144,10 @@
dependencies:
nanoid "^3.1.23"
-"@react-navigation/stack@^6.2.0":
- version "6.3.2"
- resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-6.3.2.tgz#ba0a65e10e2b165185f20718046f25d8c9abb076"
- integrity sha512-wb8koMp4OTrG5geOqEFPDatTyl8dsSyRBHN4h0wzgNT29V/JjkS3LYwkGLLfUmMfeLXFyIfEPILAjYLFmnk3dA==
+"@react-navigation/stack@^6.3.4":
+ version "6.3.4"
+ resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-6.3.4.tgz#c3b7a479aea609c0de609f91be7b2539dbae37c2"
+ integrity sha512-f4vQcbaDPSFHF1i6CnEYbA0Bnk5jRGMoCIs2/Tq0HwsUI62Mui1q5vvIlRDIi5QomJoHzhfTBp9IzMQ/sUQJlg==
dependencies:
"@react-navigation/elements" "^1.3.6"
color "^4.2.3"
@@ -2277,12 +2300,12 @@
xcode "3.0.1"
yargs "^16.2.0"
-"@shopify/flash-list@^1.2.2":
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/@shopify/flash-list/-/flash-list-1.2.2.tgz#4c4e98745303b918c2c25ce1a4711d225a022326"
- integrity sha512-hgGuvHDXdrgJRZFfL6pgVia/8NkFIYxucnjOEBFa+K0NRgzoESK3QdW++kbJhdeiVKAyROgWjygIOR3YsfrHxA==
+"@shopify/flash-list@^1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@shopify/flash-list/-/flash-list-1.4.0.tgz#cf299486ec9a7c97b7a8b1e8b6bf144a78141ed6"
+ integrity sha512-PvPOyk353LuETFnNA038+QaJsAFlCQ2TYC7DHP3YnYqTX72g2BM6qLoLsPaptXKuoXX+dinOo0MbEm7HDjTy1g==
dependencies:
- recyclerlistview "4.1.2"
+ recyclerlistview "4.2.0"
tslib "2.4.0"
"@sideway/address@^4.1.3":
@@ -2449,11 +2472,6 @@
dependencies:
"@babel/types" "^7.3.0"
-"@types/eslint-visitor-keys@^1.0.0":
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
- integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
-
"@types/graceful-fs@^4.1.2":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@@ -2514,10 +2532,10 @@
jest-matcher-utils "^27.0.0"
pretty-format "^27.0.0"
-"@types/json-schema@^7.0.3":
- version "7.0.9"
- resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
- integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
+"@types/json-schema@^7.0.9":
+ version "7.0.11"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
+ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/node@*":
version "17.0.21"
@@ -2614,6 +2632,11 @@
dependencies:
"@types/node" "*"
+"@types/semver@^7.3.12":
+ version "7.3.12"
+ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.12.tgz#920447fdd78d76b19de0438b7f60df3c4a80bf1c"
+ integrity sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==
+
"@types/stack-utils@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
@@ -2643,65 +2666,87 @@
dependencies:
"@types/yargs-parser" "*"
-"@typescript-eslint/eslint-plugin@^3.1.0":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f"
- integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ==
- dependencies:
- "@typescript-eslint/experimental-utils" "3.10.1"
- debug "^4.1.1"
- functional-red-black-tree "^1.0.1"
- regexpp "^3.0.0"
- semver "^7.3.2"
- tsutils "^3.17.1"
-
-"@typescript-eslint/experimental-utils@3.10.1":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686"
- integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==
- dependencies:
- "@types/json-schema" "^7.0.3"
- "@typescript-eslint/types" "3.10.1"
- "@typescript-eslint/typescript-estree" "3.10.1"
- eslint-scope "^5.0.0"
- eslint-utils "^2.0.0"
-
-"@typescript-eslint/parser@^3.1.0":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467"
- integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==
- dependencies:
- "@types/eslint-visitor-keys" "^1.0.0"
- "@typescript-eslint/experimental-utils" "3.10.1"
- "@typescript-eslint/types" "3.10.1"
- "@typescript-eslint/typescript-estree" "3.10.1"
- eslint-visitor-keys "^1.1.0"
-
-"@typescript-eslint/types@3.10.1":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
- integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
+"@typescript-eslint/eslint-plugin@^5.30.5":
+ version "5.40.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.40.1.tgz#3203a6ff396b1194083faaa6e5110c401201d7d5"
+ integrity sha512-FsWboKkWdytGiXT5O1/R9j37YgcjO8MKHSUmWnIEjVaz0krHkplPnYi7mwdb+5+cs0toFNQb0HIrN7zONdIEWg==
+ dependencies:
+ "@typescript-eslint/scope-manager" "5.40.1"
+ "@typescript-eslint/type-utils" "5.40.1"
+ "@typescript-eslint/utils" "5.40.1"
+ debug "^4.3.4"
+ ignore "^5.2.0"
+ regexpp "^3.2.0"
+ semver "^7.3.7"
+ tsutils "^3.21.0"
+
+"@typescript-eslint/parser@^5.30.5":
+ version "5.40.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.40.1.tgz#e7f8295dd8154d0d37d661ddd8e2f0ecfdee28dd"
+ integrity sha512-IK6x55va5w4YvXd4b3VrXQPldV9vQTxi5ov+g4pMANsXPTXOcfjx08CRR1Dfrcc51syPtXHF5bgLlMHYFrvQtg==
+ dependencies:
+ "@typescript-eslint/scope-manager" "5.40.1"
+ "@typescript-eslint/types" "5.40.1"
+ "@typescript-eslint/typescript-estree" "5.40.1"
+ debug "^4.3.4"
+
+"@typescript-eslint/scope-manager@5.40.1":
+ version "5.40.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.40.1.tgz#a7a5197dfd234622a2421ea590ee0ccc02e18dfe"
+ integrity sha512-jkn4xsJiUQucI16OLCXrLRXDZ3afKhOIqXs4R3O+M00hdQLKR58WuyXPZZjhKLFCEP2g+TXdBRtLQ33UfAdRUg==
+ dependencies:
+ "@typescript-eslint/types" "5.40.1"
+ "@typescript-eslint/visitor-keys" "5.40.1"
+
+"@typescript-eslint/type-utils@5.40.1":
+ version "5.40.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.40.1.tgz#091e4ce3bebbdb68f4980bae9dee2e4e1725f601"
+ integrity sha512-DLAs+AHQOe6n5LRraXiv27IYPhleF0ldEmx6yBqBgBLaNRKTkffhV1RPsjoJBhVup2zHxfaRtan8/YRBgYhU9Q==
+ dependencies:
+ "@typescript-eslint/typescript-estree" "5.40.1"
+ "@typescript-eslint/utils" "5.40.1"
+ debug "^4.3.4"
+ tsutils "^3.21.0"
+
+"@typescript-eslint/types@5.40.1":
+ version "5.40.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.40.1.tgz#de37f4f64de731ee454bb2085d71030aa832f749"
+ integrity sha512-Icg9kiuVJSwdzSQvtdGspOlWNjVDnF3qVIKXdJ103o36yRprdl3Ge5cABQx+csx960nuMF21v8qvO31v9t3OHw==
+
+"@typescript-eslint/typescript-estree@5.40.1":
+ version "5.40.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.40.1.tgz#9a7d25492f02c69882ce5e0cd1857b0c55645d72"
+ integrity sha512-5QTP/nW5+60jBcEPfXy/EZL01qrl9GZtbgDZtDPlfW5zj/zjNrdI2B5zMUHmOsfvOr2cWqwVdWjobCiHcedmQA==
+ dependencies:
+ "@typescript-eslint/types" "5.40.1"
+ "@typescript-eslint/visitor-keys" "5.40.1"
+ debug "^4.3.4"
+ globby "^11.1.0"
+ is-glob "^4.0.3"
+ semver "^7.3.7"
+ tsutils "^3.21.0"
+
+"@typescript-eslint/utils@5.40.1", "@typescript-eslint/utils@^5.10.0":
+ version "5.40.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.40.1.tgz#3204fb73a559d3b7bab7dc9d3c44487c2734a9ca"
+ integrity sha512-a2TAVScoX9fjryNrW6BZRnreDUszxqm9eQ9Esv8n5nXApMW0zeANUYlwh/DED04SC/ifuBvXgZpIK5xeJHQ3aw==
+ dependencies:
+ "@types/json-schema" "^7.0.9"
+ "@types/semver" "^7.3.12"
+ "@typescript-eslint/scope-manager" "5.40.1"
+ "@typescript-eslint/types" "5.40.1"
+ "@typescript-eslint/typescript-estree" "5.40.1"
+ eslint-scope "^5.1.1"
+ eslint-utils "^3.0.0"
+ semver "^7.3.7"
-"@typescript-eslint/typescript-estree@3.10.1":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853"
- integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==
+"@typescript-eslint/visitor-keys@5.40.1":
+ version "5.40.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.40.1.tgz#f3d2bf5af192f4432b84cec6fdcb387193518754"
+ integrity sha512-A2DGmeZ+FMja0geX5rww+DpvILpwo1OsiQs0M+joPWJYsiEFBLsH0y1oFymPNul6Z5okSmHpP4ivkc2N0Cgfkw==
dependencies:
- "@typescript-eslint/types" "3.10.1"
- "@typescript-eslint/visitor-keys" "3.10.1"
- debug "^4.1.1"
- glob "^7.1.6"
- is-glob "^4.0.1"
- lodash "^4.17.15"
- semver "^7.3.2"
- tsutils "^3.17.1"
-
-"@typescript-eslint/visitor-keys@3.10.1":
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931"
- integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==
- dependencies:
- eslint-visitor-keys "^1.1.0"
+ "@typescript-eslint/types" "5.40.1"
+ eslint-visitor-keys "^3.3.0"
"@walletconnect/browser-utils@^1.7.8":
version "1.7.8"
@@ -3074,7 +3119,7 @@ arr-union@^3.1.0:
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
-array-includes@^3.1.3, array-includes@^3.1.4:
+array-includes@^3.1.3:
version "3.1.4"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9"
integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==
@@ -3085,19 +3130,36 @@ array-includes@^3.1.3, array-includes@^3.1.4:
get-intrinsic "^1.1.1"
is-string "^1.0.7"
+array-includes@^3.1.5:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb"
+ integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.19.5"
+ get-intrinsic "^1.1.1"
+ is-string "^1.0.7"
+
+array-union@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
+ integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+
array-unique@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
-array.prototype.flatmap@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446"
- integrity sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==
+array.prototype.flatmap@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f"
+ integrity sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==
dependencies:
- call-bind "^1.0.0"
+ call-bind "^1.0.2"
define-properties "^1.1.3"
- es-abstract "^1.19.0"
+ es-abstract "^1.19.2"
+ es-shim-unscopables "^1.0.0"
asap@~2.0.6:
version "2.0.6"
@@ -3206,18 +3268,6 @@ babel-core@^7.0.0-bridge.0:
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
-babel-eslint@^10.1.0:
- version "10.1.0"
- resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
- integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- "@babel/parser" "^7.7.0"
- "@babel/traverse" "^7.7.0"
- "@babel/types" "^7.7.0"
- eslint-visitor-keys "^1.0.0"
- resolve "^1.12.0"
-
babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
@@ -4449,6 +4499,13 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2:
dependencies:
ms "2.1.2"
+debug@^4.3.4:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -4505,6 +4562,14 @@ define-properties@^1.1.3:
dependencies:
object-keys "^1.0.12"
+define-properties@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1"
+ integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==
+ dependencies:
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
+
define-property@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
@@ -4623,6 +4688,13 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
+dir-glob@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
+ integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
+ dependencies:
+ path-type "^4.0.0"
+
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@@ -4859,6 +4931,43 @@ es-abstract@^1.18.5, es-abstract@^1.19.0, es-abstract@^1.19.1:
string.prototype.trimstart "^1.0.4"
unbox-primitive "^1.0.1"
+es-abstract@^1.19.2, es-abstract@^1.19.5:
+ version "1.20.4"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.4.tgz#1d103f9f8d78d4cf0713edcd6d0ed1a46eed5861"
+ integrity sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==
+ dependencies:
+ call-bind "^1.0.2"
+ es-to-primitive "^1.2.1"
+ function-bind "^1.1.1"
+ function.prototype.name "^1.1.5"
+ get-intrinsic "^1.1.3"
+ get-symbol-description "^1.0.0"
+ has "^1.0.3"
+ has-property-descriptors "^1.0.0"
+ has-symbols "^1.0.3"
+ internal-slot "^1.0.3"
+ is-callable "^1.2.7"
+ is-negative-zero "^2.0.2"
+ is-regex "^1.1.4"
+ is-shared-array-buffer "^1.0.2"
+ is-string "^1.0.7"
+ is-weakref "^1.0.2"
+ object-inspect "^1.12.2"
+ object-keys "^1.1.1"
+ object.assign "^4.1.4"
+ regexp.prototype.flags "^1.4.3"
+ safe-regex-test "^1.0.0"
+ string.prototype.trimend "^1.0.5"
+ string.prototype.trimstart "^1.0.5"
+ unbox-primitive "^1.0.2"
+
+es-shim-unscopables@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241"
+ integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==
+ dependencies:
+ has "^1.0.3"
+
es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
@@ -4922,14 +5031,12 @@ escodegen@^2.0.0:
optionalDependencies:
source-map "~0.6.1"
-eslint-config-prettier@^6.10.1:
- version "6.15.0"
- resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz#7f93f6cb7d45a92f1537a70ecc06366e1ac6fed9"
- integrity sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==
- dependencies:
- get-stdin "^6.0.0"
+eslint-config-prettier@^8.5.0:
+ version "8.5.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1"
+ integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==
-eslint-plugin-eslint-comments@^3.1.2:
+eslint-plugin-eslint-comments@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz#9e1cd7b4413526abb313933071d7aba05ca12ffa"
integrity sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==
@@ -4937,69 +5044,72 @@ eslint-plugin-eslint-comments@^3.1.2:
escape-string-regexp "^1.0.5"
ignore "^5.0.5"
-eslint-plugin-flowtype@2.50.3:
- version "2.50.3"
- resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.50.3.tgz#61379d6dce1d010370acd6681740fd913d68175f"
- integrity sha512-X+AoKVOr7Re0ko/yEXyM5SSZ0tazc6ffdIOocp2fFUlWoDt7DV0Bz99mngOkAFLOAWjqRA5jPwqUCbrx13XoxQ==
+eslint-plugin-ft-flow@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.1.tgz#57d9a12ef02b7af8f9bd6ccd6bd8fa4034809716"
+ integrity sha512-dGBnCo+ok6H9p6Vw2oPFEM4vA9IEclRXQQAA/Zws51/L5zr3FDl9FxQiWGfaw0WaTIX5biiAxp/q1W5bGXjlVA==
dependencies:
- lodash "^4.17.10"
+ lodash "^4.17.21"
+ string-natural-compare "^3.0.1"
-eslint-plugin-jest@22.4.1:
- version "22.4.1"
- resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.4.1.tgz#a5fd6f7a2a41388d16f527073b778013c5189a9c"
- integrity sha512-gcLfn6P2PrFAVx3AobaOzlIEevpAEf9chTpFZz7bYfc7pz8XRv7vuKTIE4hxPKZSha6XWKKplDQ0x9Pq8xX2mg==
+eslint-plugin-jest@^26.5.3:
+ version "26.9.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-26.9.0.tgz#7931c31000b1c19e57dbfb71bbf71b817d1bf949"
+ integrity sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==
+ dependencies:
+ "@typescript-eslint/utils" "^5.10.0"
-eslint-plugin-prettier@3.1.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba"
- integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==
+eslint-plugin-prettier@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b"
+ integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==
dependencies:
prettier-linter-helpers "^1.0.0"
-eslint-plugin-react-hooks@^4.0.4:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172"
- integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==
+eslint-plugin-react-hooks@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3"
+ integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==
eslint-plugin-react-native-globals@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2"
integrity sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==
-eslint-plugin-react-native@^3.8.1:
- version "3.11.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react-native/-/eslint-plugin-react-native-3.11.0.tgz#c73b0886abb397867e5e6689d3a6a418682e6bac"
- integrity sha512-7F3OTwrtQPfPFd+VygqKA2VZ0f2fz0M4gJmry/TRE18JBb94/OtMxwbL7Oqwu7FGyrdeIOWnXQbBAveMcSTZIA==
+eslint-plugin-react-native@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-native/-/eslint-plugin-react-native-4.0.0.tgz#eec41984abe4970bdd7c6082dff7a98a5e34d0bb"
+ integrity sha512-kMmdxrSY7A1WgdqaGC+rY/28rh7kBGNBRsk48ovqkQmdg5j4K+DaFmegENDzMrdLkoufKGRNkKX6bgSwQTCAxQ==
dependencies:
"@babel/traverse" "^7.7.4"
eslint-plugin-react-native-globals "^0.1.1"
-eslint-plugin-react@^7.20.0:
- version "7.29.3"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.3.tgz#f4eab757f2756d25d6d4c2a58a9e20b004791f05"
- integrity sha512-MzW6TuCnDOcta67CkpDyRfRsEVx9FNMDV8wZsDqe1luHPdGTrQIUaUXD27Ja3gHsdOIs/cXzNchWGlqm+qRVRg==
+eslint-plugin-react@^7.30.1:
+ version "7.31.10"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz#6782c2c7fe91c09e715d536067644bbb9491419a"
+ integrity sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==
dependencies:
- array-includes "^3.1.4"
- array.prototype.flatmap "^1.2.5"
+ array-includes "^3.1.5"
+ array.prototype.flatmap "^1.3.0"
doctrine "^2.1.0"
estraverse "^5.3.0"
jsx-ast-utils "^2.4.1 || ^3.0.0"
minimatch "^3.1.2"
object.entries "^1.1.5"
object.fromentries "^2.0.5"
- object.hasown "^1.1.0"
+ object.hasown "^1.1.1"
object.values "^1.1.5"
prop-types "^15.8.1"
resolve "^2.0.0-next.3"
semver "^6.3.0"
- string.prototype.matchall "^4.0.6"
+ string.prototype.matchall "^4.0.7"
eslint-plugin-simple-import-sort@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-7.0.0.tgz#a1dad262f46d2184a90095a60c66fef74727f0f8"
integrity sha512-U3vEDB5zhYPNfxT5TYR7u01dboFZp+HNpnGhkDB2g/2E4wZ/g1Q9Ton8UwCLfRV9yAKyYqDh62oHOamvkFxsvw==
-eslint-scope@^5.0.0, eslint-scope@^5.1.1:
+eslint-scope@5.1.1, eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
@@ -5007,23 +5117,35 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.1:
esrecurse "^4.3.0"
estraverse "^4.1.1"
-eslint-utils@^2.0.0, eslint-utils@^2.1.0:
+eslint-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
dependencies:
eslint-visitor-keys "^1.1.0"
-eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
+eslint-utils@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672"
+ integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==
+ dependencies:
+ eslint-visitor-keys "^2.0.0"
+
+eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
-eslint-visitor-keys@^2.0.0:
+eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
+eslint-visitor-keys@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
+ integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
+
eslint@^7.32.0:
version "7.32.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
@@ -5262,6 +5384,17 @@ fast-diff@^1.1.2:
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
+fast-glob@^3.2.9:
+ version "3.2.12"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
+ integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@@ -5277,6 +5410,13 @@ fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.0.7:
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
+fastq@^1.6.0:
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
+ integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
+ dependencies:
+ reusify "^1.0.4"
+
fb-watchman@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85"
@@ -5548,11 +5688,26 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+function.prototype.name@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621"
+ integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.3"
+ es-abstract "^1.19.0"
+ functions-have-names "^1.2.2"
+
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
+functions-have-names@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
+ integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
+
gauge@~2.7.3:
version "2.7.4"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
@@ -5586,16 +5741,20 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
+get-intrinsic@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385"
+ integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==
+ dependencies:
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.3"
+
get-package-type@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
-get-stdin@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
- integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
-
get-stream@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
@@ -5687,6 +5846,18 @@ globals@^9.18.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
+globby@^11.1.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
+ integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
+ dependencies:
+ array-union "^2.1.0"
+ dir-glob "^3.0.1"
+ fast-glob "^3.2.9"
+ ignore "^5.2.0"
+ merge2 "^1.4.1"
+ slash "^3.0.0"
+
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
version "4.2.9"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
@@ -5714,6 +5885,11 @@ has-bigints@^1.0.1:
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
+has-bigints@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
+ integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
+
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -5724,7 +5900,14 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-has-symbols@^1.0.1, has-symbols@^1.0.2:
+has-property-descriptors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861"
+ integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==
+ dependencies:
+ get-intrinsic "^1.1.1"
+
+has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
@@ -5932,7 +6115,7 @@ ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
-ignore@^5.0.5:
+ignore@^5.0.5, ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
@@ -6123,6 +6306,11 @@ is-callable@^1.1.4, is-callable@^1.2.4:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
+is-callable@^1.2.7:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
+ integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
+
is-ci@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
@@ -6237,7 +6425,7 @@ is-generator-function@^1.0.7:
dependencies:
has-tostringtag "^1.0.0"
-is-glob@^4.0.0, is-glob@^4.0.1:
+is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
@@ -6257,7 +6445,7 @@ is-nan@^1.2.1:
call-bind "^1.0.0"
define-properties "^1.1.3"
-is-negative-zero@^2.0.1:
+is-negative-zero@^2.0.1, is-negative-zero@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==
@@ -6311,6 +6499,13 @@ is-shared-array-buffer@^1.0.1:
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6"
integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==
+is-shared-array-buffer@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
+ integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==
+ dependencies:
+ call-bind "^1.0.2"
+
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -6356,7 +6551,7 @@ is-unicode-supported@^0.1.0:
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
-is-weakref@^1.0.1:
+is-weakref@^1.0.1, is-weakref@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==
@@ -7316,7 +7511,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
-lodash@^4.17.10, lodash@^4.17.12, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
+lodash@^4.17.12, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -7424,6 +7619,11 @@ merge-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+merge2@^1.3.0, merge2@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
methods@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -7747,7 +7947,7 @@ mime-db@1.51.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
-"mime-db@>= 1.43.0 < 2":
+mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
@@ -7759,6 +7959,13 @@ mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.34:
dependencies:
mime-db "1.51.0"
+mime-types@^2.1.35:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
mime@1.6.0, mime@^1.3.4:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
@@ -8060,6 +8267,11 @@ object-inspect@^1.11.0, object-inspect@^1.9.0:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
+object-inspect@^1.12.2:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
+ integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
+
object-is@^1.0.1:
version "1.1.5"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
@@ -8090,6 +8302,16 @@ object.assign@^4.1.0, object.assign@^4.1.2:
has-symbols "^1.0.1"
object-keys "^1.1.1"
+object.assign@^4.1.4:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
+ integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ has-symbols "^1.0.3"
+ object-keys "^1.1.1"
+
object.entries@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861"
@@ -8108,13 +8330,13 @@ object.fromentries@^2.0.5:
define-properties "^1.1.3"
es-abstract "^1.19.1"
-object.hasown@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.0.tgz#7232ed266f34d197d15cac5880232f7a4790afe5"
- integrity sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==
+object.hasown@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.1.tgz#ad1eecc60d03f49460600430d97f23882cf592a3"
+ integrity sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==
dependencies:
- define-properties "^1.1.3"
- es-abstract "^1.19.1"
+ define-properties "^1.1.4"
+ es-abstract "^1.19.5"
object.pick@^1.1.1, object.pick@^1.3.0:
version "1.3.0"
@@ -8559,10 +8781,10 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
-prettier@^2.0.2:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
- integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
+prettier@^2.7.1:
+ version "2.7.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64"
+ integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
pretty-format@^26.5.2, pretty-format@^26.6.2:
version "26.6.2"
@@ -8881,10 +9103,10 @@ react-native-crypto@^2.0.1, react-native-crypto@^2.2.0:
public-encrypt "^4.0.0"
randomfill "^1.0.3"
-react-native-device-info@^10.2.0:
- version "10.2.0"
- resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-10.2.0.tgz#be17a1b86cabbe53f4adde96b662204784d3cefd"
- integrity sha512-7VycjFGaLKIZLDwCxSK5cWZdDOGqz3QxdGF57B/a4bW3gkrUFa5Gf0v7fUR9YL/VBJM0UwDiDH7pR9hkMQt+vw==
+react-native-device-info@^10.3.0:
+ version "10.3.0"
+ resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-10.3.0.tgz#6bab64d84d3415dd00cc446c73ec5e2e61fddbe7"
+ integrity sha512-/ziZN1sA1REbJTv5mQZ4tXggcTvSbct+u5kCaze8BmN//lbxcTvWsU6NQd4IihLt89VkbX+14IGc9sVApSxd/w==
react-native-document-picker@^8.1.1:
version "8.1.1"
@@ -8998,10 +9220,10 @@ react-native-randombytes@^3.6.1:
buffer "^4.9.1"
sjcl "^1.0.3"
-react-native-reanimated@^2.10.0:
- version "2.10.0"
- resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.10.0.tgz#ed53be66bbb553b5b5e93e93ef4217c87b8c73db"
- integrity sha512-jKm3xz5nX7ABtHzzuuLmawP0pFWP77lXNdIC6AWOceBs23OHUaJ29p4prxr/7Sb588GwTbkPsYkDqVFaE3ezNQ==
+react-native-reanimated@^2.12.0:
+ version "2.12.0"
+ resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.12.0.tgz#5821eecfb1769b1617a67a2d4dec12fdeedb2b6e"
+ integrity sha512-nrlPyw+Hx9u4iJhZk9PoTvDo/QmVAd+bo7OK9Tv3hveNEF9++5oig/g3Uv9V93shy9avTYGsUprUvAEt/xdzeQ==
dependencies:
"@babel/plugin-transform-object-assign" "^7.16.7"
"@babel/preset-typescript" "^7.16.7"
@@ -9038,10 +9260,10 @@ react-native-svg-transformer@^1.0.0:
"@svgr/plugin-svgo" "^6.1.2"
path-dirname "^1.0.2"
-react-native-svg@^13.4.0:
- version "13.4.0"
- resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.4.0.tgz#82399ba0956c454144618aa581e2d748dd3f010a"
- integrity sha512-B3TwK+H0+JuRhYPzF21AgqMt4fjhCwDZ9QUtwNstT5XcslJBXC0FoTkdZo8IEb1Sv4suSqhZwlAY6lwOv3tHag==
+react-native-svg@^13.5.0:
+ version "13.5.0"
+ resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.5.0.tgz#2b34f0ab84436b3c1e57109d62012a6c031e0596"
+ integrity sha512-JWou/k4zfXRY6TsVgM4i30D0z54McqQ64D5cAfOzX17ThZwFjOheN24owmbAs7Y+naD0Bk5j409kT/6FZEop4g==
dependencies:
css-select "^5.1.0"
css-tree "^1.1.3"
@@ -9281,10 +9503,10 @@ recursive-fs@^2.1.0:
resolved "https://registry.yarnpkg.com/recursive-fs/-/recursive-fs-2.1.0.tgz#1e20cf7836b292ed81208c4817550a58ad0e15ff"
integrity sha512-oed3YruYsD52Mi16s/07eYblQOLi5dTtxpIJNdfCEJ7S5v8dDgVcycar0pRWf4IBuPMIkoctC8RTqGJzIKMNAQ==
-recyclerlistview@4.1.2:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/recyclerlistview/-/recyclerlistview-4.1.2.tgz#3629c2faff13be3dc64bca82490e9a5efb2303aa"
- integrity sha512-fvopyPoXaDY/RJGJKzroGYHgBAZoXlEdLw1D4PSi3isTRhyfbh0WNNuTyLfJSiz6ctZeb0J2ErPCInYcahFTlw==
+recyclerlistview@4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/recyclerlistview/-/recyclerlistview-4.2.0.tgz#a140149aaa470c9787a1426452651934240d69ef"
+ integrity sha512-uuBCi0c+ggqHKwrzPX4Z/mJOzsBbjZEAwGGmlwpD/sD7raXixdAbdJ6BTcAmuWG50Cg4ru9p12M94Njwhr/27A==
dependencies:
lodash.debounce "4.0.8"
prop-types "15.8.1"
@@ -9349,7 +9571,7 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
-regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.1:
+regexp.prototype.flags@^1.2.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307"
integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==
@@ -9357,7 +9579,16 @@ regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.1:
call-bind "^1.0.2"
define-properties "^1.1.3"
-regexpp@^3.0.0, regexpp@^3.1.0:
+regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
+ integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.3"
+ functions-have-names "^1.2.2"
+
+regexpp@^3.1.0, regexpp@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
@@ -9453,7 +9684,7 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
-resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.18.1:
+resolve@^1.10.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.18.1:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
@@ -9491,6 +9722,11 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
+reusify@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+ integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
@@ -9555,7 +9791,7 @@ run-async@^2.2.0, run-async@^2.4.0:
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
-run-parallel@^1.1.2:
+run-parallel@^1.1.2, run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
@@ -9586,6 +9822,15 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2,
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+safe-regex-test@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
+ integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==
+ dependencies:
+ call-bind "^1.0.2"
+ get-intrinsic "^1.1.3"
+ is-regex "^1.1.4"
+
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@@ -9663,6 +9908,13 @@ semver@^7.2.1, semver@^7.3.2, semver@^7.3.5:
dependencies:
lru-cache "^6.0.0"
+semver@^7.3.7:
+ version "7.3.8"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
+ integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
+ dependencies:
+ lru-cache "^6.0.0"
+
send@0.17.2:
version "0.17.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820"
@@ -10058,6 +10310,11 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
+string-natural-compare@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
+ integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
+
string-width@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -10084,18 +10341,18 @@ string-width@^2.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
-string.prototype.matchall@^4.0.6:
- version "4.0.6"
- resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa"
- integrity sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==
+string.prototype.matchall@^4.0.7:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d"
+ integrity sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.3"
es-abstract "^1.19.1"
get-intrinsic "^1.1.1"
- has-symbols "^1.0.2"
+ has-symbols "^1.0.3"
internal-slot "^1.0.3"
- regexp.prototype.flags "^1.3.1"
+ regexp.prototype.flags "^1.4.1"
side-channel "^1.0.4"
string.prototype.trimend@^1.0.4:
@@ -10106,6 +10363,15 @@ string.prototype.trimend@^1.0.4:
call-bind "^1.0.2"
define-properties "^1.1.3"
+string.prototype.trimend@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0"
+ integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.19.5"
+
string.prototype.trimstart@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
@@ -10114,6 +10380,15 @@ string.prototype.trimstart@^1.0.4:
call-bind "^1.0.2"
define-properties "^1.1.3"
+string.prototype.trimstart@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef"
+ integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ es-abstract "^1.19.5"
+
string_decoder@^1.1.1, string_decoder@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -10481,7 +10756,7 @@ tslib@^2.0.1, tslib@^2.1.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
-tsutils@^3.17.1:
+tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
@@ -10567,6 +10842,16 @@ unbox-primitive@^1.0.1:
has-symbols "^1.0.2"
which-boxed-primitive "^1.0.2"
+unbox-primitive@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
+ integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==
+ dependencies:
+ call-bind "^1.0.2"
+ has-bigints "^1.0.2"
+ has-symbols "^1.0.3"
+ which-boxed-primitive "^1.0.2"
+
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"