diff --git a/locale/da/texts.po b/locale/da/texts.po
index 987506761..d5cb9eac0 100644
--- a/locale/da/texts.po
+++ b/locale/da/texts.po
@@ -335,14 +335,21 @@ msgstr "Symbolet er en kortere version af token-navnet"
msgid "E.g. HTR"
msgstr "F.eks. HTR"
-#: src/screens/Dashboard.js:67 src/screens/RegisterTokenManual.js:147
+#. Only show the toggle button when Nano Contract is enabled to the wallet
+#: src/screens/Dashboard.js:130 src/screens/Dashboard.js:187
+msgid "Tokens"
+msgstr "Tokens"
+
+#. Only show the toggle button when Nano Contract is enabled to the wallet
+#: src/components/NanoContract/NanoContractsList.js:71
+#: src/screens/Dashboard.js:131
+msgid "Nano Contracts"
+msgstr ""
+
+#: src/screens/Dashboard.js:178 src/screens/RegisterTokenManual.js:147
msgid "Register token"
msgstr "Registrer token"
-#: src/screens/Dashboard.js:74
-msgid "TOKENS"
-msgstr "TOKENS"
-
#: src/screens/InitWallet.js:61
msgid "Welcome to Hathor Wallet!"
msgstr "Velkommen til Hathor Wallet!"
@@ -987,29 +994,29 @@ msgstr "Transaktion"
msgid "Open"
msgstr "Åben"
-#: src/components/AskForPushNotification.js:22
+#: src/components/AskForPushNotification.js:29
msgid "Do you want to enable push notifications for this wallet?"
msgstr ""
-#: src/components/AskForPushNotification.js:23
+#: src/components/AskForPushNotification.js:30
msgid "You can always change this later in the settings menu"
msgstr ""
-#: src/components/AskForPushNotification.js:24
+#: src/components/AskForPushNotification.js:31
msgid "Yes, enable"
msgstr ""
-#: src/components/AskForPushNotificationRefresh.js:28
+#: src/components/AskForPushNotificationRefresh.js:35
msgid "Refresh your push notification registration"
msgstr ""
-#: src/components/AskForPushNotificationRefresh.js:29
+#: src/components/AskForPushNotificationRefresh.js:36
msgid ""
"In order to keep receiving push notifications, you need to refresh your "
"registration. Do you want to do it now?"
msgstr ""
-#: src/components/AskForPushNotificationRefresh.js:30
+#: src/components/AskForPushNotificationRefresh.js:37
msgid "Refresh"
msgstr ""
@@ -1090,15 +1097,15 @@ msgstr "Ingen internetforbindelse"
msgid "Public Explorer"
msgstr "Public Explorer"
-#: src/components/PushTxDetailsModal.js:69 src/components/TxDetailsModal.js:103
+#: src/components/PushTxDetailsModal.js:76 src/components/TxDetailsModal.js:101
msgid "Date & Time"
msgstr "Dato & Tid"
-#: src/components/PushTxDetailsModal.js:70
+#: src/components/PushTxDetailsModal.js:77
msgid "ID"
msgstr "ID"
-#: src/components/PushTxDetailsModal.js:94
+#: src/components/PushTxDetailsModal.js:101
msgid "New Transaction"
msgstr ""
@@ -1119,16 +1126,16 @@ msgstr "Del"
msgid "Propagating transaction to the network."
msgstr ""
-#: src/components/ShowPushNotificationTxDetails.js:8
+#: src/components/ShowPushNotificationTxDetails.js:15
msgid "Transation not found"
msgstr ""
-#: src/components/ShowPushNotificationTxDetails.js:9
+#: src/components/ShowPushNotificationTxDetails.js:16
msgid ""
"The transaction has not arrived yet in your wallet. Do you want to retry?"
msgstr ""
-#: src/components/ShowPushNotificationTxDetails.js:10
+#: src/components/ShowPushNotificationTxDetails.js:17
msgid "Retry"
msgstr ""
@@ -1212,5 +1219,16 @@ msgstr ""
msgid "Custom network"
msgstr ""
-#~ msgid "I understand the risks of using a mobile wallet"
-#~ msgstr "Jeg forstår risikoen ved at bruge en mobil wallet"
+#: src/components/NanoContract/NoNanoContracts.js:16
+msgid "No Nano Contracts"
+msgstr ""
+
+#: src/components/NanoContract/NoNanoContracts.js:18
+msgid ""
+"You can keep track of your registered Nano Contracts here once you have "
+"registered them."
+msgstr ""
+
+#: src/components/NanoContract/RegisterNewNanoContractButton.js:22
+msgid "Register new"
+msgstr ""
diff --git a/locale/pt-br/texts.po b/locale/pt-br/texts.po
index 9a44a469a..dae12e7e5 100644
--- a/locale/pt-br/texts.po
+++ b/locale/pt-br/texts.po
@@ -344,14 +344,21 @@ msgstr "O símbolo é a versão reduzida do nome do seu token"
msgid "E.g. HTR"
msgstr "Por exemplo, HTR"
-#: src/screens/Dashboard.js:67 src/screens/RegisterTokenManual.js:147
+#. Only show the toggle button when Nano Contract is enabled to the wallet
+#: src/screens/Dashboard.js:130 src/screens/Dashboard.js:187
+msgid "Tokens"
+msgstr "Tokens"
+
+#. Only show the toggle button when Nano Contract is enabled to the wallet
+#: src/components/NanoContract/NanoContractsList.js:71
+#: src/screens/Dashboard.js:131
+msgid "Nano Contracts"
+msgstr "Nano Contracts"
+
+#: src/screens/Dashboard.js:178 src/screens/RegisterTokenManual.js:147
msgid "Register token"
msgstr "Registrar um token"
-#: src/screens/Dashboard.js:74
-msgid "TOKENS"
-msgstr "TOKENS"
-
#: src/screens/InitWallet.js:61
msgid "Welcome to Hathor Wallet!"
msgstr "Bem vindo à Hathor Wallet!"
@@ -1014,23 +1021,23 @@ msgstr "Transação"
msgid "Open"
msgstr "Abrir"
-#: src/components/AskForPushNotification.js:22
+#: src/components/AskForPushNotification.js:29
msgid "Do you want to enable push notifications for this wallet?"
msgstr "Você deseja habilitar as notificações para esta wallet?"
-#: src/components/AskForPushNotification.js:23
+#: src/components/AskForPushNotification.js:30
msgid "You can always change this later in the settings menu"
msgstr "Você sempre pode alterar as configurações depois no menu"
-#: src/components/AskForPushNotification.js:24
+#: src/components/AskForPushNotification.js:31
msgid "Yes, enable"
msgstr "Sim, habilitar"
-#: src/components/AskForPushNotificationRefresh.js:28
+#: src/components/AskForPushNotificationRefresh.js:35
msgid "Refresh your push notification registration"
msgstr "Atualize sua configuração de notificação"
-#: src/components/AskForPushNotificationRefresh.js:29
+#: src/components/AskForPushNotificationRefresh.js:36
msgid ""
"In order to keep receiving push notifications, you need to refresh your "
"registration. Do you want to do it now?"
@@ -1038,7 +1045,7 @@ msgstr ""
"Para continuar recebendo notificações, é necessário atualizar sua "
"configuração de notificação. Deseja atualizar agora?"
-#: src/components/AskForPushNotificationRefresh.js:30
+#: src/components/AskForPushNotificationRefresh.js:37
msgid "Refresh"
msgstr "Atualizar"
@@ -1120,15 +1127,15 @@ msgstr "Sem conexão com a internet"
msgid "Public Explorer"
msgstr "Explorer Público"
-#: src/components/PushTxDetailsModal.js:69 src/components/TxDetailsModal.js:103
+#: src/components/PushTxDetailsModal.js:76 src/components/TxDetailsModal.js:101
msgid "Date & Time"
msgstr "Data & Hora"
-#: src/components/PushTxDetailsModal.js:70
+#: src/components/PushTxDetailsModal.js:77
msgid "ID"
msgstr "ID"
-#: src/components/PushTxDetailsModal.js:94
+#: src/components/PushTxDetailsModal.js:101
msgid "New Transaction"
msgstr "Transação"
@@ -1149,16 +1156,16 @@ msgstr "Compartilhar"
msgid "Propagating transaction to the network."
msgstr "Propagando a transação para a rede."
-#: src/components/ShowPushNotificationTxDetails.js:8
+#: src/components/ShowPushNotificationTxDetails.js:15
msgid "Transation not found"
msgstr "Transação não encontrada"
-#: src/components/ShowPushNotificationTxDetails.js:9
+#: src/components/ShowPushNotificationTxDetails.js:16
msgid ""
"The transaction has not arrived yet in your wallet. Do you want to retry?"
msgstr "A transação ainda não chegou na sua carteira. Deseja tentar novamente?"
-#: src/components/ShowPushNotificationTxDetails.js:10
+#: src/components/ShowPushNotificationTxDetails.js:17
msgid "Retry"
msgstr "Tentar novamente"
@@ -1248,14 +1255,16 @@ msgstr ""
msgid "Custom network"
msgstr "Rede personalizada"
-#~ msgid "There has been an error upgrading your wallet."
-#~ msgstr "Ocorreu um erro ao atualizar sua wallet."
-
-#~ msgid "Please reset it and restore it with your seed."
-#~ msgstr "Por favor resete sua wallet e restaure utilizando sua seed."
+#: src/components/NanoContract/NoNanoContracts.js:16
+msgid "No Nano Contracts"
+msgstr "Nenhum Nano Contract"
-#~ msgid "Upgrading your wallet, please hang on."
-#~ msgstr "Atualizando sua wallet, por favor aguarde."
+#: src/components/NanoContract/NoNanoContracts.js:18
+msgid ""
+"You can keep track of your registered Nano Contracts here once you have "
+"registered them."
+msgstr "Você pode acompanhar os Nano Contracts registrados nesta tela."
-#~ msgid "I understand the risks of using a mobile wallet"
-#~ msgstr "Eu entendo os riscos de usar uma wallet de celular"
+#: src/components/NanoContract/RegisterNewNanoContractButton.js:22
+msgid "Register new"
+msgstr "Registrar Nano Contract"
diff --git a/locale/ru-ru/texts.po b/locale/ru-ru/texts.po
index 79aa9eeca..d06c178fe 100644
--- a/locale/ru-ru/texts.po
+++ b/locale/ru-ru/texts.po
@@ -336,14 +336,21 @@ msgstr "Символ является сокращенной версией им
msgid "E.g. HTR"
msgstr "Например, HTR"
-#: src/screens/Dashboard.js:67 src/screens/RegisterTokenManual.js:147
+#. Only show the toggle button when Nano Contract is enabled to the wallet
+#: src/screens/Dashboard.js:130 src/screens/Dashboard.js:187
+msgid "Tokens"
+msgstr "ТокенЫ"
+
+#. Only show the toggle button when Nano Contract is enabled to the wallet
+#: src/components/NanoContract/NanoContractsList.js:71
+#: src/screens/Dashboard.js:131
+msgid "Nano Contracts"
+msgstr ""
+
+#: src/screens/Dashboard.js:178 src/screens/RegisterTokenManual.js:147
msgid "Register token"
msgstr "Зарегистрировать токен"
-#: src/screens/Dashboard.js:74
-msgid "TOKENS"
-msgstr "ТОКЕНЫ"
-
#: src/screens/InitWallet.js:61
msgid "Welcome to Hathor Wallet!"
msgstr "Добро пожаловать в Hathor Wallet!"
@@ -991,29 +998,29 @@ msgstr ""
msgid "Open"
msgstr "Открыть"
-#: src/components/AskForPushNotification.js:22
+#: src/components/AskForPushNotification.js:29
msgid "Do you want to enable push notifications for this wallet?"
msgstr ""
-#: src/components/AskForPushNotification.js:23
+#: src/components/AskForPushNotification.js:30
msgid "You can always change this later in the settings menu"
msgstr ""
-#: src/components/AskForPushNotification.js:24
+#: src/components/AskForPushNotification.js:31
msgid "Yes, enable"
msgstr ""
-#: src/components/AskForPushNotificationRefresh.js:28
+#: src/components/AskForPushNotificationRefresh.js:35
msgid "Refresh your push notification registration"
msgstr ""
-#: src/components/AskForPushNotificationRefresh.js:29
+#: src/components/AskForPushNotificationRefresh.js:36
msgid ""
"In order to keep receiving push notifications, you need to refresh your "
"registration. Do you want to do it now?"
msgstr ""
-#: src/components/AskForPushNotificationRefresh.js:30
+#: src/components/AskForPushNotificationRefresh.js:37
msgid "Refresh"
msgstr ""
@@ -1080,15 +1087,15 @@ msgstr "Нет соединения с интернетом"
msgid "Public Explorer"
msgstr "Public Explorer"
-#: src/components/PushTxDetailsModal.js:69 src/components/TxDetailsModal.js:103
+#: src/components/PushTxDetailsModal.js:76 src/components/TxDetailsModal.js:101
msgid "Date & Time"
msgstr "Дата и Время"
-#: src/components/PushTxDetailsModal.js:70
+#: src/components/PushTxDetailsModal.js:77
msgid "ID"
msgstr "ID"
-#: src/components/PushTxDetailsModal.js:94
+#: src/components/PushTxDetailsModal.js:101
msgid "New Transaction"
msgstr ""
@@ -1109,16 +1116,16 @@ msgstr "Поделиться"
msgid "Propagating transaction to the network."
msgstr ""
-#: src/components/ShowPushNotificationTxDetails.js:8
+#: src/components/ShowPushNotificationTxDetails.js:15
msgid "Transation not found"
msgstr ""
-#: src/components/ShowPushNotificationTxDetails.js:9
+#: src/components/ShowPushNotificationTxDetails.js:16
msgid ""
"The transaction has not arrived yet in your wallet. Do you want to retry?"
msgstr ""
-#: src/components/ShowPushNotificationTxDetails.js:10
+#: src/components/ShowPushNotificationTxDetails.js:17
msgid "Retry"
msgstr ""
@@ -1201,5 +1208,16 @@ msgstr ""
msgid "Custom network"
msgstr ""
-#~ msgid "I understand the risks of using a mobile wallet"
-#~ msgstr "Я осознаю риски использования мобильного кошелька"
+#: src/components/NanoContract/NoNanoContracts.js:16
+msgid "No Nano Contracts"
+msgstr ""
+
+#: src/components/NanoContract/NoNanoContracts.js:18
+msgid ""
+"You can keep track of your registered Nano Contracts here once you have "
+"registered them."
+msgstr ""
+
+#: src/components/NanoContract/RegisterNewNanoContractButton.js:22
+msgid "Register new"
+msgstr ""
diff --git a/locale/texts.pot b/locale/texts.pot
index 7234fe558..ffc40232d 100644
--- a/locale/texts.pot
+++ b/locale/texts.pot
@@ -328,13 +328,21 @@ msgstr ""
msgid "E.g. HTR"
msgstr ""
-#: src/screens/Dashboard.js:67
-#: src/screens/RegisterTokenManual.js:147
-msgid "Register token"
+#: src/screens/Dashboard.js:130
+#: src/screens/Dashboard.js:187
+#. Only show the toggle button when Nano Contract is enabled to the wallet
+msgid "Tokens"
msgstr ""
-#: src/screens/Dashboard.js:74
-msgid "TOKENS"
+#: src/components/NanoContract/NanoContractsList.js:71
+#: src/screens/Dashboard.js:131
+#. Only show the toggle button when Nano Contract is enabled to the wallet
+msgid "Nano Contracts"
+msgstr ""
+
+#: src/screens/Dashboard.js:178
+#: src/screens/RegisterTokenManual.js:147
+msgid "Register token"
msgstr ""
#: src/screens/InitWallet.js:61
@@ -982,29 +990,29 @@ msgstr ""
msgid "Open"
msgstr ""
-#: src/components/AskForPushNotification.js:22
+#: src/components/AskForPushNotification.js:29
msgid "Do you want to enable push notifications for this wallet?"
msgstr ""
-#: src/components/AskForPushNotification.js:23
+#: src/components/AskForPushNotification.js:30
msgid "You can always change this later in the settings menu"
msgstr ""
-#: src/components/AskForPushNotification.js:24
+#: src/components/AskForPushNotification.js:31
msgid "Yes, enable"
msgstr ""
-#: src/components/AskForPushNotificationRefresh.js:28
+#: src/components/AskForPushNotificationRefresh.js:35
msgid "Refresh your push notification registration"
msgstr ""
-#: src/components/AskForPushNotificationRefresh.js:29
+#: src/components/AskForPushNotificationRefresh.js:36
msgid ""
"In order to keep receiving push notifications, you need to refresh your "
"registration. Do you want to do it now?"
msgstr ""
-#: src/components/AskForPushNotificationRefresh.js:30
+#: src/components/AskForPushNotificationRefresh.js:37
msgid "Refresh"
msgstr ""
@@ -1071,16 +1079,16 @@ msgstr ""
msgid "Public Explorer"
msgstr ""
-#: src/components/PushTxDetailsModal.js:69
-#: src/components/TxDetailsModal.js:103
+#: src/components/PushTxDetailsModal.js:76
+#: src/components/TxDetailsModal.js:101
msgid "Date & Time"
msgstr ""
-#: src/components/PushTxDetailsModal.js:70
+#: src/components/PushTxDetailsModal.js:77
msgid "ID"
msgstr ""
-#: src/components/PushTxDetailsModal.js:94
+#: src/components/PushTxDetailsModal.js:101
msgid "New Transaction"
msgstr ""
@@ -1102,15 +1110,15 @@ msgstr ""
msgid "Propagating transaction to the network."
msgstr ""
-#: src/components/ShowPushNotificationTxDetails.js:8
+#: src/components/ShowPushNotificationTxDetails.js:15
msgid "Transation not found"
msgstr ""
-#: src/components/ShowPushNotificationTxDetails.js:9
+#: src/components/ShowPushNotificationTxDetails.js:16
msgid "The transaction has not arrived yet in your wallet. Do you want to retry?"
msgstr ""
-#: src/components/ShowPushNotificationTxDetails.js:10
+#: src/components/ShowPushNotificationTxDetails.js:17
msgid "Retry"
msgstr ""
@@ -1191,3 +1199,17 @@ msgstr ""
#: src/components/NetworkSettings/NetworkStatusBar.js:14
msgid "Custom network"
msgstr ""
+
+#: src/components/NanoContract/NoNanoContracts.js:16
+msgid "No Nano Contracts"
+msgstr ""
+
+#: src/components/NanoContract/NoNanoContracts.js:18
+msgid ""
+"You can keep track of your registered Nano Contracts here once you have "
+"registered them."
+msgstr ""
+
+#: src/components/NanoContract/RegisterNewNanoContractButton.js:22
+msgid "Register new"
+msgstr ""
diff --git a/src/components/AskForPushNotification.js b/src/components/AskForPushNotification.js
index f1a468d89..b091c30a9 100644
--- a/src/components/AskForPushNotification.js
+++ b/src/components/AskForPushNotification.js
@@ -1,3 +1,10 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { t } from 'ttag';
diff --git a/src/components/AskForPushNotificationRefresh.js b/src/components/AskForPushNotificationRefresh.js
index a613ccf88..e8cbf0173 100644
--- a/src/components/AskForPushNotificationRefresh.js
+++ b/src/components/AskForPushNotificationRefresh.js
@@ -1,3 +1,10 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { t } from 'ttag';
diff --git a/src/components/HathorHeader.js b/src/components/HathorHeader.js
index d6952d7e3..493a9e404 100644
--- a/src/components/HathorHeader.js
+++ b/src/components/HathorHeader.js
@@ -16,59 +16,102 @@ import chevronLeft from '../assets/icons/chevron-left.png';
import closeIcon from '../assets/icons/icCloseActive.png';
import { COLORS, STYLE } from '../styles/themes';
-const HathorHeader = (props) => {
- const renderBackButton = () => {
- if (props.onBackPress) {
- return (
-
-
-
-
-
- );
- }
- return ;
- };
+const HathorHeader = ({
+ title,
+ rightElement,
+ withLogo,
+ withBorder,
+ onBackPress,
+ onCancel,
+ wrapperStyle,
+ children,
+}) => {
+ const hasChildren = children != null;
+ const left = React.Children.toArray(children).find(
+ (child) => child.type.displayName === HathorHeaderLeft.displayName
+ );
+ const right = React.Children.toArray(children).find(
+ (child) => child.type.displayName === HathorHeaderRight.displayName
+ );
- const CancelButton = () => (
-
+ return (
+
+ {hasChildren
+ && (
+
+ {left}
+ {right}
+
+ )}
+ {!hasChildren
+ && (
+
+
+
+
+
+ )}
+
);
+};
+
+const Wrapper = ({ withBorder, style, children }) => (
+
+ {children}
+
+);
+
+const InnerWrapper = ({ children }) => (
+
+ {children}
+
+);
- const renderHeaderRight = () => {
- const element = (props.onCancel ? : props.rightElement);
+const HathorHeaderLeft = ({ children }) => ({children});
+HathorHeaderLeft.displayName = 'HathorHeaderLeft';
+
+const HathorHeaderRight = ({ children }) => {children};
+HathorHeaderRight.displayName = 'HathorHeaderRight';
+
+HathorHeader.Left = HathorHeaderLeft;
+HathorHeader.Right = HathorHeaderRight;
+
+const CancelButton = ({ onCancel }) => (
+
+);
+
+const LeftComponent = ({ onBackPress }) => {
+ if (onBackPress) {
return (
-
- {element}
+
+
+
+
);
- };
-
- const renderHeaderCentral = () => {
- if (props.withLogo) {
- return (
-
- );
- }
- return {props.title};
- };
+ }
+ return ;
+};
- let extraStyle = {};
- if (props.withBorder) {
- extraStyle = { borderBottomWidth: 1 };
+const CentralComponent = ({ title, withLogo }) => {
+ if (withLogo) {
+ return (
+
+ );
}
+ return {title};
+};
+const RightComponent = ({ rightElement, onCancel }) => {
+ const element = (onCancel ? : rightElement);
return (
-
-
- {renderBackButton()}
- {renderHeaderCentral()}
- {renderHeaderRight()}
-
+
+ {element}
);
};
@@ -81,6 +124,9 @@ const styles = StyleSheet.create({
borderColor: COLORS.borderColor,
paddingHorizontal: 16,
},
+ wrapperWithBorder: {
+ borderBottomWidth: 1,
+ },
innerWrapper: {
flex: 1,
flexDirection: 'row',
@@ -93,5 +139,16 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
},
+ iconWrapperStart: {
+ justifyContent: 'flex-start',
+ },
+ iconWrapperEnd: {
+ justifyContent: 'flex-end',
+ },
+ centralComponentLogo: {
+ height: 22,
+ width: 100,
+ },
});
+
export default HathorHeader;
diff --git a/src/components/NanoContract/NanoContractIcon.icon.js b/src/components/NanoContract/NanoContractIcon.icon.js
new file mode 100644
index 000000000..4a3737bd4
--- /dev/null
+++ b/src/components/NanoContract/NanoContractIcon.icon.js
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import * as React from 'react'
+import Svg, { Path } from 'react-native-svg'
+
+/**
+ * @param {SvgProps} props
+ *
+ * @description
+ * Svg converted from Figma using transaformer at https://react-svgr.com/playground/?native=true
+ */
+export const NanoContractIcon = (props) => (
+
+);
diff --git a/src/components/NanoContract/NanoContractsList.js b/src/components/NanoContract/NanoContractsList.js
new file mode 100644
index 000000000..a150f3569
--- /dev/null
+++ b/src/components/NanoContract/NanoContractsList.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import { StyleSheet, View, Text } from 'react-native';
+import { t } from 'ttag';
+import { useNavigation } from '@react-navigation/native';
+
+import { useSelector } from 'react-redux';
+import { COLORS } from '../../styles/themes';
+import HathorHeader from '../HathorHeader';
+import { NoNanoContracts } from './NoNanoContracts';
+import { RegisterNanoContract } from './RegisterNewNanoContractButton';
+import { NanoContractsListItem } from './NanoContractsListItem';
+import { HathorFlatList } from '../HathorFlatList';
+
+/**
+ * @param {Object} state Redux root state
+ * @returns {Object} Array of registered Nano Contract with basic information
+ */
+const getRegisteredNanoContracts = (state) => {
+ const { registered } = state.nanoContract;
+ return Object.values(registered);
+}
+
+export const NanoContractsList = () => {
+ const registeredNanoContracts = useSelector(getRegisteredNanoContracts);
+ const navigation = useNavigation();
+
+ const navigatesToNanoContractTransactions = () => {
+ navigation.navigate('NanoContractTransactions');
+ };
+ const isEmpty = () => registeredNanoContracts.length === 0;
+ const notEmpty = () => !isEmpty();
+
+ return (
+
+
+ {isEmpty()
+ && }
+ {notEmpty()
+ && (
+ (
+
+ )}
+ keyExtractor={(nc) => nc.ncId}
+ />
+ )}
+
+ );
+};
+
+const Wrapper = ({ children }) => (
+
+ {children}
+
+);
+
+const Header = () => (
+
+
+ {t`Nano Contracts`}
+
+
+
+
+
+);
+
+const ListWrapper = ({ children }) => (
+
+ {children}
+
+);
+
+const styles = StyleSheet.create({
+ wrapper: {
+ flex: 1,
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ backgroundColor: COLORS.lowContrastDetail, // Defines an outer area on the main list content
+ },
+ listWrapper: {
+ flex: 1,
+ justifyContent: 'center',
+ alignSelf: 'stretch',
+ marginTop: 16,
+ marginBottom: 45,
+ backgroundColor: COLORS.backgroundColor,
+ marginHorizontal: 16,
+ borderRadius: 16,
+ shadowOffset: { height: 2, width: 0 },
+ shadowRadius: 4,
+ shadowColor: COLORS.textColor,
+ shadowOpacity: 0.08,
+ },
+ headerTitle: {
+ fontSize: 24,
+ lineHeight: 24,
+ fontWeight: 'bold',
+ },
+});
diff --git a/src/components/NanoContract/NanoContractsListItem.js b/src/components/NanoContract/NanoContractsListItem.js
new file mode 100644
index 000000000..dde25bac4
--- /dev/null
+++ b/src/components/NanoContract/NanoContractsListItem.js
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import { TouchableHighlight, StyleSheet, View, Text, Image } from 'react-native';
+
+import chevronRight from '../../assets/icons/chevron-right.png';
+import { COLORS } from '../../styles/themes';
+import { getShortHash } from '../../utils';
+import { NanoContractIcon } from './NanoContractIcon.icon';
+
+/**
+ * Renders each item of Nano Contract List.
+ *
+ * @param {Object} ncItem
+ * @property {Object} ncItem.item registered Nano Contract data
+ * @property {() => {}} ncItem.onPress A void function to be called when item is pressed.
+ */
+export const NanoContractsListItem = ({ item, onPress }) => (
+
+
+
+
+
+);
+
+const Wrapper = ({ onPress, children }) => (
+
+ {children}
+
+);
+
+const Icon = () => (
+
+
+
+);
+
+/**
+ * Renders item core content.
+ *
+ * @param {Object} ncItem
+ * @property {Obeject} ncItem.nc registered Nano Contract data
+ */
+const ContentWrapper = ({ nc }) => (
+
+ Nano Contract ID
+ {getShortHash(nc.ncId, 7)}
+ Blueprint Name
+ {nc.blueprintName}
+
+);
+
+const ArrowRight = () => (
+
+
+
+);
+
+const styles = StyleSheet.create({
+ wrapper: {
+ paddingVertical: 16,
+ paddingHorizontal: 16,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ contentWrapper: {
+ maxWidth: '80%',
+ flexDirection: 'column',
+ alignItems: 'flex-start',
+ justifyContent: 'space-between',
+ marginRight: 'auto',
+ paddingHorizontal: 16,
+ },
+ icon: {
+ paddingVertical: 6,
+ paddingHorizontal: 8,
+ backgroundColor: COLORS.primary,
+ alignSelf: 'flex-start',
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: 8,
+ },
+ text: {
+ fontSize: 14,
+ lineHeight: 20,
+ paddingBottom: 6,
+ color: COLORS.textLabel,
+ },
+ property: {
+ paddingBottom: 4,
+ fontWeight: 'bold',
+ color: 'black',
+ },
+ padding0: {
+ paddingBottom: 0,
+ },
+});
diff --git a/src/components/NanoContract/NoNanoContracts.js b/src/components/NanoContract/NoNanoContracts.js
new file mode 100644
index 000000000..a8e18dcab
--- /dev/null
+++ b/src/components/NanoContract/NoNanoContracts.js
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import { StyleSheet, View, Text } from 'react-native';
+import { t } from 'ttag';
+
+import { RegisterNanoContract } from './RegisterNewNanoContractButton';
+
+export const NoNanoContracts = () => (
+
+ {t`No Nano Contracts`}
+
+ {t`You can keep track of your registered Nano Contracts here once you have registered them.`}
+
+
+
+);
+
+const styles = StyleSheet.create({
+ wrapper: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingHorizontal: 45,
+ /* Play the role of a minimum vertical padding for small screens */
+ paddingVertical: 90,
+ },
+ title: {
+ fontSize: 16,
+ lineHeight: 20,
+ fontWeight: 'bold',
+ paddingBottom: 16,
+ },
+ content: {
+ fontSize: 14,
+ lineHeight: 20,
+ paddingBottom: 16,
+ textAlign: 'center',
+ },
+ learnMoreWrapper: {
+ display: 'inline-block',
+ /* We are using negative margin here to correct the text position
+ * and create an optic effect of alignment. */
+ marginBottom: -4,
+ paddingLeft: 2,
+ },
+ learnMoreContainer: {
+ justifyContent: 'flex-start',
+ borderBottomWidth: 1,
+ },
+ learnMoreText: {
+ fontSize: 14,
+ lineHeight: 20,
+ fontWeight: 'bold',
+ color: 'black',
+ },
+});
diff --git a/src/components/NanoContract/RegisterNewNanoContractButton.js b/src/components/NanoContract/RegisterNewNanoContractButton.js
new file mode 100644
index 000000000..a924dbced
--- /dev/null
+++ b/src/components/NanoContract/RegisterNewNanoContractButton.js
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import { t } from 'ttag';
+import { useNavigation } from '@react-navigation/native';
+
+import SimpleButton from '../SimpleButton';
+
+export const RegisterNanoContract = () => {
+ const navigation = useNavigation();
+ const navigatesToRegisterNanoContract = () => {
+ navigation.navigate('RegisterNanoContract');
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/PushTxDetailsModal.js b/src/components/PushTxDetailsModal.js
index 6a8eee367..2b9bfdb53 100644
--- a/src/components/PushTxDetailsModal.js
+++ b/src/components/PushTxDetailsModal.js
@@ -1,3 +1,10 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
import React from 'react';
import { Text, StyleSheet, View } from 'react-native';
import { t } from 'ttag';
diff --git a/src/components/ShowPushNotificationTxDetails.js b/src/components/ShowPushNotificationTxDetails.js
index b16819901..fe9a87243 100644
--- a/src/components/ShowPushNotificationTxDetails.js
+++ b/src/components/ShowPushNotificationTxDetails.js
@@ -1,3 +1,10 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { t } from 'ttag';
diff --git a/src/components/SimpleButton.js b/src/components/SimpleButton.js
index 9dfef6f11..c7adc79f1 100644
--- a/src/components/SimpleButton.js
+++ b/src/components/SimpleButton.js
@@ -11,14 +11,29 @@ import {
} from 'react-native';
import { PRIMARY_COLOR } from '../constants';
+/**
+ * Simple button component.
+ *
+ * @typedef {Object} Props
+ * @property {string} [title] - The title text of the button.
+ * @property {Object} [textStyle] - The style object for the text of the button.
+ * @property {string} [color] - The color of button's text.
+ * @property {string} [icon] - The icon component to be displayed in the button.
+ * @property {Object} [iconStyle] - The style object for the icon component.
+ * @property {Object} [containerStyle] - The style object for the container of the button.
+ * @property {Function} onPress - The function to be called when the button is pressed.
+ * @property {Object} children - The children component to be rendered.
+ *
+ * @param {Props} props - The props for the SimpleButton component.
+ */
const SimpleButton = ({
title,
+ textStyle,
color,
icon,
- onPress,
- containerStyle,
- textStyle,
iconStyle,
+ containerStyle,
+ onPress,
children
}) => {
const renderTitle = () => {
@@ -46,10 +61,7 @@ const SimpleButton = ({
};
return (
-
+
{renderTitle()}
{renderIcon()}
{children}
diff --git a/src/components/TwoOptionsToggle.js b/src/components/TwoOptionsToggle.js
new file mode 100644
index 000000000..af02df398
--- /dev/null
+++ b/src/components/TwoOptionsToggle.js
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React, { useState } from 'react';
+import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';
+import { COLORS } from '../styles/themes';
+
+/**
+ * @param {{
+ * options: Map<'first'|'second', { value: string; onTap: Function; }>;
+ * defaultOption: 'first'|'second';
+ * }}
+ */
+export const TwoOptionsToggle = ({ options, defaultOption }) => {
+ const [currOption, setCurrOption] = useState(defaultOption);
+ const isFirst = currOption === 'first';
+ const isSecond = currOption === 'second';
+
+ const onTapFirst = () => onTap('first');
+ const onTapSecond = () => onTap('second');
+ const onTap = (option) => {
+ if (option === currOption) {
+ // do nothing and halt.
+ return;
+ }
+ setCurrOption(option);
+ // Execute the callback assigned to the option
+ options[option].onTap();
+ };
+
+ return (
+
+
+
+
+ );
+};
+
+/**
+ * @param {{
+ * optionValue: string;
+ * isActive: boolean;
+ * onTap: (option: string) => void;
+ * }}
+ */
+const Option = ({ optionValue, isActive, onTap }) => (
+
+ {optionValue}
+
+);
+
+const styles = StyleSheet.create({
+ wrapper: {
+ display: 'flex',
+ flexDirection: 'row',
+ width: '80%',
+ marginTop: 16,
+ borderRadius: 24,
+ backgroundColor: 'hsla(220, 10%, 94%, 1)',
+ },
+ button: {
+ width: '50%',
+ borderRadius: 24,
+ paddingTop: 9,
+ paddingBottom: 10,
+ color: COLORS.textColor,
+ },
+ buttonFocus: {
+ backgroundColor: COLORS.backgroundColor,
+ },
+ text: {
+ fontSize: 14,
+ lineHeight: 20,
+ textAlign: 'center',
+ },
+ textFocus: {
+ fontWeight: 'bold',
+ },
+});
diff --git a/src/components/WalletConnect/ModalButton.js b/src/components/WalletConnect/ModalButton.js
index 85c9222b9..c78b19695 100644
--- a/src/components/WalletConnect/ModalButton.js
+++ b/src/components/WalletConnect/ModalButton.js
@@ -1,3 +1,10 @@
+/**
+ * Copyright (c) Hathor Labs and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
import React from 'react';
import { Text, TouchableOpacity, StyleSheet } from 'react-native';
import { COLORS } from '../../styles/themes';
diff --git a/src/models.js b/src/models.js
index 7425c3199..dec798466 100644
--- a/src/models.js
+++ b/src/models.js
@@ -16,7 +16,7 @@ export class TxHistory {
* timestamp: number;
* tokenUid: string;
* balance: number;
- * voided: boolean;
+ * isVoided: boolean;
* version: number;
* ncId?: string;
* ncMethod?: string;
diff --git a/src/screens/Dashboard.js b/src/screens/Dashboard.js
index d6b8143c3..7f3326a5e 100644
--- a/src/screens/Dashboard.js
+++ b/src/screens/Dashboard.js
@@ -5,95 +5,208 @@
* LICENSE file in the root directory of this source tree.
*/
-import React from 'react';
-import { View } from 'react-native';
-import { connect } from 'react-redux';
+import React, { useState } from 'react';
+import { StyleSheet, View, Text } from 'react-native';
+import { useDispatch, useSelector } from 'react-redux';
import { t } from 'ttag';
import { get } from 'lodash';
+import { useNavigation } from '@react-navigation/native';
import AskForPushNotification from '../components/AskForPushNotification';
import HathorHeader from '../components/HathorHeader';
import TokenSelect from '../components/TokenSelect';
import SimpleButton from '../components/SimpleButton';
import OfflineBar from '../components/OfflineBar';
+import { TwoOptionsToggle } from '../components/TwoOptionsToggle';
import { tokenFetchBalanceRequested, updateSelectedToken } from '../actions';
import ShowPushNotificationTxDetails from '../components/ShowPushNotificationTxDetails';
import AskForPushNotificationRefresh from '../components/AskForPushNotificationRefresh';
+import { COLORS } from '../styles/themes';
+import { TOKEN_DOWNLOAD_STATUS } from '../sagas/tokens';
+import { NanoContractsList } from '../components/NanoContract/NanoContractsList';
+import { getNanoContractFeatureToggle } from '../utils';
/**
- * tokens {Array} array with all added tokens on this wallet
- * tokensBalance {Object} dict with balance for each token
- * selectedToken {Object} token currently selected by the user
- * tokenMetadata {Object} metadata of tokens
+ * State filter to retrieve token-related data from root state.
+ *
+ * @typedef {Object} TokenData
+ * @property {string} selectedToken - Current token selected.
+ * @property {string[]} tokens - Array containing all the tokens registered on the wallet.
+ * @property {{ [uid: string]: Object }} tokensBalance - Map of balance per token.
+ * @property {{ [uid: string]: Object }} tokensMetadata - Map of token's metadata per token.
+ *
+ * @returns {TokenData} Token-related data obtained from the root state.
*/
-const mapStateToProps = (state) => ({
+const getTokensState = (state) => ({
+ selectedToken: state.selectedToken,
tokens: state.tokens,
tokensBalance: state.tokensBalance,
- tokensHistory: state.tokensHistory,
- selectedToken: state.selectedToken,
- tokenMetadata: state.tokenMetadata,
- tokenLoadingState: state.tokenLoadingState,
+ tokensMetadata: state.tokenMetadata,
});
-const mapDispatchToProps = (dispatch) => ({
- updateSelectedToken: (token) => dispatch(updateSelectedToken(token)),
- getBalance: (token) => dispatch(tokenFetchBalanceRequested(token)),
-});
+// Check if the token balance is already loaded
+/**
+ * @param {{ [uid: string]: Object }} tokensBalance a map of token's balance per token uid
+ * @param {{ uid: string }} token the token data
+ * @returns {string} the status of the current tokens balance loading process.
+ */
+const getTokensBalanceStatus = (tokensBalance, token) => get(tokensBalance, `${token.uid}.status`, TOKEN_DOWNLOAD_STATUS.LOADING);
+
+/**
+ * @param {string} status the current status from tokens balance loading process.
+ * @returns {boolean} `true` if loading, `false` otherwise.
+ */
+const isTokensBalanceLoading = (status) => status === TOKEN_DOWNLOAD_STATUS.LOADING;
+
+/**
+ * @param {string} status the current status from tokens balance loading process.
+ * @returns {boolean} `true` if failed, `false` otherwise.
+ */
+const isTokensBalanceFailed = (status) => status === TOKEN_DOWNLOAD_STATUS.FAILED;
+
+/**
+ * Enum for the list component that can be selected to render on Dashboard.
+ * @readonly
+ * @enum {string}
+ */
+const listOption = {
+ tokens: 'tokens',
+ nanoContracts: 'nanoContracts',
+};
+
+/**
+ * @param {listOption} currList the list component selected to be rendered.
+ * @returns {boolean} `true` if tokens list is selected, `false` otherwise.
+ */
+const isTokensSelected = (currList) => currList === listOption.tokens;
+
+/**
+ * @param {listOption} currList the list component selected to be rendered.
+ * @returns {boolean} `true` if nanoContracts list is selected, `false` otherwise.
+ */
+const isNanoContractsSelected = (currList) => currList === listOption.nanoContracts;
+
+export const Dashboard = () => {
+ const {
+ tokens,
+ tokensBalance,
+ selectedToken,
+ tokensMetadata,
+ } = useSelector(getTokensState);
+ const isNanoContractEnabled = useSelector(getNanoContractFeatureToggle);
-class Dashboard extends React.Component {
- static navigatorStyle = { tabBarVisible: false }
+ const [currList, selectList] = useState(listOption.tokens);
+ const navigation = useNavigation();
+ const dispatch = useDispatch();
- onItemPress = (item) => {
- // Check if the token balance is already loaded
- const tokenBalanceStatus = get(this.props.tokensBalance, `${item.uid}.status`, 'loading');
+ const onTokenPress = (token) => {
+ const status = getTokensBalanceStatus(tokensBalance, token);
- if (tokenBalanceStatus === 'loading') {
+ if (isTokensBalanceLoading(status)) {
return;
}
- if (tokenBalanceStatus === 'failed') {
+ if (isTokensBalanceFailed(status)) {
// If the token balance status is failed, we should try again
- this.props.getBalance(item.uid);
+ dispatch(tokenFetchBalanceRequested(token.uid))
return;
}
- this.props.updateSelectedToken(item);
- this.props.navigation.navigate('MainScreen');
+ dispatch(updateSelectedToken(token));
+ navigation.navigate('MainScreen');
}
- render() {
- const ManualInfoButton = () => (
- this.props.navigation.navigate('RegisterToken')}
- />
- );
-
- const Header = () => (
- }
- />
- );
-
- return (
-
-
-
-
- }
- renderArrow
- onItemPress={this.onItemPress}
- selectedToken={this.props.selectedToken}
- tokens={this.props.tokens}
- tokensBalance={this.props.tokensBalance}
- tokenMetadata={this.props.tokenMetadata}
- />
-
-
- );
- }
+ return (
+
+
+
+
+ { // Only show the toggle button when Nano Contract is enabled to the wallet
+ isNanoContractEnabled
+ && (
+
+ selectList(listOption.tokens) },
+ second: { value: t`Nano Contracts`, onTap: () => selectList(listOption.nanoContracts) }
+ }}
+ defaultOption='first'
+ />
+
+ )
+ }
+ { // Default behavior is to show tokens list
+ isTokensSelected(currList)
+ && (
+ }
+ renderArrow
+ onItemPress={onTokenPress}
+ selectedToken={selectedToken}
+ tokens={tokens}
+ tokensBalance={tokensBalance}
+ tokenMetadata={tokensMetadata}
+ />
+ )
+ }
+ { // Only show if Nano Contract is enabled in the wallet
+ isNanoContractEnabled
+ && isNanoContractsSelected(currList)
+ &&
+ }
+
+
+ );
}
-export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
+const Wrapper = ({ children }) => (
+
+ {children}
+
+);
+
+const DashBoardHeader = ({ children }) => (
+
+ {children}
+
+);
+
+const RegisterToken = () => {
+ const navigation = useNavigation();
+ return (
+ navigation.navigate('RegisterToken')}
+ />
+ );
+};
+
+const TokensHeader = () => (
+
+
+ {t`Tokens`}
+
+
+
+
+
+);
+
+const styles = StyleSheet.create({
+ wrapper: {
+ flex: 1,
+ },
+ headerWrapper: {
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'center',
+ backgroundColor: COLORS.lowContrastDetail,
+ },
+ headerTitle: {
+ fontSize: 24,
+ lineHeight: 24,
+ fontWeight: 'bold',
+ },
+});
+
+export default Dashboard;
diff --git a/src/styles/themes.js b/src/styles/themes.js
index e574f2278..ca13a8bf9 100644
--- a/src/styles/themes.js
+++ b/src/styles/themes.js
@@ -92,6 +92,10 @@ export const COLORS = {
feedbackError600: 'hsla(7, 100%, 30%, 1)',
freeze100: 'hsla(0, 0%, 90%, 1)',
freeze300: 'hsla(0, 0%, 45%, 1)',
+ /**
+ * @type {string} Black with 38% of light and full opaque
+ */
+ textLabel: 'hsla(0, 0%, 38%, 1)',
};
/**
diff --git a/src/utils.js b/src/utils.js
index 35dfc0abb..e950f1354 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -12,7 +12,7 @@ import { t } from 'ttag';
import { Linking, Platform, Text } from 'react-native';
import { getStatusBarHeight } from 'react-native-status-bar-height';
import baseStyle from './styles/init';
-import { KEYCHAIN_USER } from './constants';
+import { KEYCHAIN_USER, NANO_CONTRACT_FEATURE_TOGGLE } from './constants';
import { STORE } from './store';
import { TxHistory } from './models';
import { COLORS, STYLE } from './styles/themes';
@@ -394,45 +394,12 @@ export const isPushNotificationAvailableForUser = (state) => (
);
/**
- * Verifies if all critical built-in object prototypes are frozen, indicating a secure
- * ECMAScript (SES) environment. This check ensures that the execution context remains
- * immutable by preventing modifications to built-in prototypes, a common target for
- * tampering in JavaScript environments.
+ * Get Nano Contract feature toggle state from redux.
*
- * By freezing prototypes, SES aims to prevent malicious or accidental interference
- * that could compromise application integrity or lead to security vulnerabilities.
+ * @param {Object} state Redux store state
*
- * @returns {boolean} Returns true if all specified built-in prototypes are frozen
- * indicating a secure and immutable execution environment. Returns false if any
- * prototype is not frozen, suggesting potential security risks.
+ * @returns {boolean} the Nano Contract feature toggle state.
*/
-export const verifySesEnabled = () => {
- const prototypes = [
- Array.prototype,
- ArrayBuffer.prototype,
- Boolean.prototype,
- Date.prototype,
- Error.prototype,
- Function.prototype,
- Map.prototype,
- Number.prototype,
- Object.prototype,
- RegExp.prototype,
- Set.prototype,
- String.prototype,
- Symbol.prototype,
- WeakMap.prototype,
- WeakSet.prototype,
- Float32Array.prototype,
- Float64Array.prototype,
- Int8Array.prototype,
- Int16Array.prototype,
- Int32Array.prototype,
- Uint8Array.prototype,
- Uint8ClampedArray.prototype,
- Uint16Array.prototype,
- Uint32Array.prototype,
- ];
-
- return prototypes.every(Object.isFrozen);
-};
+export const getNanoContractFeatureToggle = (state) => (
+ state.featureToggles[NANO_CONTRACT_FEATURE_TOGGLE]
+);