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] +);