diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5c236d630..d16b915db 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,3 +2,4 @@ Please go the the `Preview` tab and select the appropriate Pull Request template * [Feature Branch](?expand=1&template=feature_branch_pr_template.md) - Use this template when you are creating a feature branch pull-request * [Version Bump](?expand=1&template=version_bump_pr_template.md) - Use this template when you are creating a version bump pull-request +* [Release Candidate](?expand=1&template=release_candidate_pr_template.md) - Use this template when you are starting a new release-candidate process diff --git a/.github/PULL_REQUEST_TEMPLATE/release_candidate_pr_template.md b/.github/PULL_REQUEST_TEMPLATE/release_candidate_pr_template.md new file mode 100644 index 000000000..db61eced3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/release_candidate_pr_template.md @@ -0,0 +1,8 @@ + +[//]: # (This should be discussed in the daily meeting) +[//]: # (Remember to bump the version to the next release candidate after this is merged) + +### Description + +Start the release candidate process for version `X.X.X-rc.1`. +This PR will merge any changes on `master` into `release-candidate` branch. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2224aaf79..a71681774 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,13 +3,15 @@ on: push: branches: - master - - dev + - release + - release-candidate tags: - v* pull_request: branches: - - dev - master + - release + - release-candidate jobs: test: runs-on: 'ubuntu-latest' diff --git a/.gitignore b/.gitignore index dbf2c6c04..c1798d7a2 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,6 @@ ios/HathorMobile.xcodeproj/project.xcworkspace/ .metro-health-check* # testing /coverage + +# Env file used by pre_release.sh. +/env diff --git a/android/app/build.gradle b/android/app/build.gradle index 8c0ea910c..e01054374 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -79,8 +79,8 @@ android { applicationId "network.hathor.wallet" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 63 - versionName "0.26.1" + versionCode 69 + versionName "0.27.0-rc.6" missingDimensionStrategy "react-native-camera", "general" } signingConfigs { diff --git a/ios/HathorMobile.xcodeproj/project.pbxproj b/ios/HathorMobile.xcodeproj/project.pbxproj index 2869af2a4..1740df2e5 100644 --- a/ios/HathorMobile.xcodeproj/project.pbxproj +++ b/ios/HathorMobile.xcodeproj/project.pbxproj @@ -512,7 +512,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = HathorMobile/HathorMobile.entitlements; - CURRENT_PROJECT_VERSION = 1.0.0; + CURRENT_PROJECT_VERSION = 0.6.0; DEVELOPMENT_TEAM = 55SHY647CG; ENABLE_BITCODE = NO; INFOPLIST_FILE = HathorMobile/Info.plist; @@ -521,7 +521,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.26.1; + MARKETING_VERSION = 0.27.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -542,7 +542,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = HathorMobile/HathorMobile.entitlements; - CURRENT_PROJECT_VERSION = 1.0.0; + CURRENT_PROJECT_VERSION = 0.6.0; DEVELOPMENT_TEAM = 55SHY647CG; INFOPLIST_FILE = HathorMobile/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; @@ -550,7 +550,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 0.26.1; + MARKETING_VERSION = 0.27.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/locale/da/texts.po b/locale/da/texts.po index 5c5020b49..c22598be6 100644 --- a/locale/da/texts.po +++ b/locale/da/texts.po @@ -107,19 +107,19 @@ msgstr "" msgid "New transaction received" msgstr "Ingen transaktioner" -#: src/screens/About.js:82 +#: src/screens/About.js:83 msgid "ABOUT" msgstr "OM" -#: src/screens/About.js:94 +#: src/screens/About.js:95 msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "Denne app er udviklet af Hathor Labs og distribueres gratis." -#: src/screens/About.js:98 src/screens/InitWallet.js:64 +#: src/screens/About.js:99 src/screens/InitWallet.js:64 msgid "This wallet is connected to the **mainnet**." msgstr "Denne wallet er tilsluttet til ** mainnet **." -#: src/screens/About.js:101 src/screens/InitWallet.js:67 +#: src/screens/About.js:102 src/screens/InitWallet.js:67 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as pocket " @@ -129,7 +129,7 @@ msgstr "" "Så vi råder dig til kun at opbevare en lille mængde tokens her, såsom " "lommepenge." -#: src/screens/About.js:106 +#: src/screens/About.js:107 msgid "" "For further information, check out the |link1:Terms of Service| and |link2:" "Privacy Policy|, or our website |link3:https://hathor.network/|." @@ -206,30 +206,30 @@ msgstr "PIN-koder stemmer ikke overens. Prøv igen." msgid "Start the Wallet" msgstr "Start Wallet" -#: src/screens/CreateTokenAmount.js:119 src/screens/CreateTokenConfirm.js:163 +#: src/screens/CreateTokenAmount.js:120 src/screens/CreateTokenConfirm.js:163 #: src/screens/CreateTokenDepositNotice.js:42 src/screens/CreateTokenName.js:43 #: src/screens/CreateTokenSymbol.js:62 msgid "CREATE TOKEN" msgstr "OPRET TOKEN" -#: src/screens/CreateTokenAmount.js:127 +#: src/screens/CreateTokenAmount.js:128 #, javascript-format msgid "Amount of ${ this.name } (${ this.symbol })" msgstr "Antal ${ this.name } (${ this.symbol })" -#: src/screens/CreateTokenAmount.js:139 +#: src/screens/CreateTokenAmount.js:140 msgid "Deposit:" msgstr "Indsæt:" -#: src/screens/CreateTokenAmount.js:143 +#: src/screens/CreateTokenAmount.js:144 #, javascript-format msgid "You have ${ amountAvailableText } HTR available" msgstr "Du har ${ amountAvailableText } HTR tilgængelig" -#: src/screens/CreateTokenAmount.js:148 src/screens/CreateTokenName.js:64 +#: src/screens/CreateTokenAmount.js:149 src/screens/CreateTokenName.js:64 #: src/screens/CreateTokenSymbol.js:84 src/screens/InitWallet.js:220 #: src/screens/InitWallet.js:341 src/screens/SendAddressInput.js:66 -#: src/screens/SendAmountInput.js:179 +#: src/screens/SendAmountInput.js:185 msgid "Next" msgstr "Næste" @@ -425,30 +425,30 @@ msgstr "Ord" msgid "Enter your seed words separated by space" msgstr "Indtast dine seed-ord adskilt med mellemrum" -#: src/screens/LoadHistoryScreen.js:67 +#: src/screens/LoadHistoryScreen.js:51 src/screens/LoadWalletErrorScreen.js:20 +msgid "Try again" +msgstr "" + +#: src/screens/LoadHistoryScreen.js:60 msgid "Loading your transactions" msgstr "Indlæser dine transaktioner" -#: src/screens/LoadHistoryScreen.js:70 +#: src/screens/LoadHistoryScreen.js:63 #, javascript-format -msgid "**${ _this.props.loadedData.transactions } transactions** found" -msgstr "**${ _this.props.loadedData.transactions } transaktioner** fundet" +msgid "**${ loadedData.transactions } transactions** found" +msgstr "**${ props.loadedData.transactions } transaktioner** fundet" -#: src/screens/LoadHistoryScreen.js:73 +#: src/screens/LoadHistoryScreen.js:66 #, javascript-format -msgid "**${ _this.props.loadedData.addresses } addresses** found" -msgstr "**${ _this.props.loadedData.addresses } adresser** fundet" +msgid "**${ loadedData.addresses } addresses** found" +msgstr "**${ props.loadedData.addresses } adresser** fundet" #: src/screens/LoadWalletErrorScreen.js:19 msgid "There's been an error connecting to the server." msgstr "" -#: src/screens/LoadWalletErrorScreen.js:20 -msgid "Try again" -msgstr "" - #: src/screens/LoadWalletErrorScreen.js:21 src/screens/PinScreen.js:268 -#: src/screens/Settings.js:151 +#: src/screens/Settings.js:154 msgid "Reset wallet" msgstr "Nulstil wallet" @@ -530,7 +530,7 @@ msgstr "Lås Hathor-wallet op" msgid "Cancel" msgstr "Annuller" -#: src/screens/PushNotification.js:58 src/screens/Settings.js:125 +#: src/screens/PushNotification.js:58 src/screens/Settings.js:128 msgid "Push Notification" msgstr "" @@ -559,7 +559,7 @@ msgstr "" msgid "Payment Request" msgstr "Betalingsanmodning" -#: src/screens/Receive.js:106 +#: src/screens/Receive.js:107 msgid "RECEIVE" msgstr "MODTAG" @@ -657,18 +657,18 @@ msgstr "SEND" msgid "Address to send" msgstr "Adresse, der skal sendes til" -#: src/screens/SendAmountInput.js:105 +#: src/screens/SendAmountInput.js:110 msgid "Insufficient funds" msgstr "Utilstrækkelige midler" -#: src/screens/SendAmountInput.js:136 src/screens/SendConfirmScreen.js:125 +#: src/screens/SendAmountInput.js:141 src/screens/SendConfirmScreen.js:125 #, javascript-format msgid "${ amountAndToken } available" msgid_plural "${ amountAndToken } available" msgstr[0] "${ amountAndToken } tilgængelig" -msgstr[1] "${ amountAndToken } tilgængelig\n" +msgstr[1] "${ amountAndToken } tilgængelig" -#: src/screens/SendAmountInput.js:148 src/screens/SendConfirmScreen.js:133 +#: src/screens/SendAmountInput.js:154 src/screens/SendConfirmScreen.js:133 msgid "SEND ${ tokenNameUpperCase }" msgstr "SEND ${ tokenNameUpperCase }" @@ -677,11 +677,11 @@ msgstr "SEND ${ tokenNameUpperCase }" msgid "Your transfer is being processed" msgstr "Din overførsel behandles" -#: src/sagas/helpers.js:132 src/screens/SendConfirmScreen.js:104 +#: src/sagas/helpers.js:136 src/screens/SendConfirmScreen.js:104 msgid "Enter your 6-digit pin to authorize operation" msgstr "Indtast din 6-cifrede pin for at godkende overførslen" -#: src/sagas/helpers.js:133 src/screens/SendConfirmScreen.js:105 +#: src/sagas/helpers.js:137 src/screens/SendConfirmScreen.js:105 msgid "Authorize operation" msgstr "Autoriserer overførslen" @@ -693,7 +693,7 @@ msgstr "Din overførsel af **${ _this.amountAndToken }** er bekræftet" msgid "Address" msgstr "Adresse" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:258 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:288 #: src/screens/SendConfirmScreen.js:168 msgid "Send" msgstr "Send" @@ -716,43 +716,43 @@ msgstr "Du har ikke den anmodede token [${ tokenLabel }]" msgid "Scan the QR code" msgstr "Scan QR-koden" -#: src/screens/Settings.js:94 +#: src/screens/Settings.js:97 msgid "You are connected to" msgstr "Du er tilsluttet til" -#: src/screens/Settings.js:102 +#: src/screens/Settings.js:105 msgid "General Settings" msgstr "" -#: src/screens/Settings.js:106 +#: src/screens/Settings.js:109 msgid "Connected to" msgstr "Forbundet til" -#: src/screens/Settings.js:119 +#: src/screens/Settings.js:122 msgid "Security" msgstr "Sikkerhed" -#: src/screens/Settings.js:132 +#: src/screens/Settings.js:135 msgid "Create a new token" msgstr "Opret en ny token" -#: src/screens/Settings.js:139 +#: src/screens/Settings.js:142 msgid "Register a token" msgstr "Registrer en token" -#: src/screens/Settings.js:155 +#: src/screens/Settings.js:158 msgid "About" msgstr "Om" -#: src/screens/Settings.js:162 +#: src/screens/Settings.js:165 msgid "Unique app identifier" msgstr "" -#: src/screens/Settings.js:174 +#: src/screens/Settings.js:179 msgid "Developer Settings" msgstr "" -#: src/screens/Settings.js:176 +#: src/screens/Settings.js:181 msgid "Network Settings" msgstr "" @@ -760,11 +760,11 @@ msgstr "" msgid "Unregister" msgstr "Afmeld" -#: src/screens/UnregisterToken.js:112 +#: src/screens/UnregisterToken.js:108 msgid "UNREGISTER TOKEN" msgstr "AFREGISTRER TOKEN" -#: src/screens/UnregisterToken.js:117 +#: src/screens/UnregisterToken.js:113 msgid "" "If you unregister this token **you won't be able to execute operations with " "it**, unless you register it again." @@ -772,17 +772,17 @@ msgstr "" "Hvis du afregistrerer denne token, **kan du ikke udføre funktioner med " "den**, medmindre du registrerer den igen." -#: src/screens/UnregisterToken.js:120 +#: src/screens/UnregisterToken.js:116 msgid "" "You won't lose your tokens, they will just not appear on this wallet anymore." msgstr "Du mister ikke dine tokens, de vises bare ikke længere i denne wallet." -#: src/screens/UnregisterToken.js:124 +#: src/screens/UnregisterToken.js:120 #, javascript-format msgid "I want to unregister the token **${ tokenLabel }**" msgstr "Jeg vil afregistrere token **${ tokenLabel }**" -#: src/screens/UnregisterToken.js:139 +#: src/screens/UnregisterToken.js:135 msgid "Unregister token" msgstr "Afregistrer token" @@ -831,70 +831,78 @@ msgstr "" msgid "Manual" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:17 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:18 msgid "Custom Network Settings" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:18 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:19 msgid "" "Any token outside mainnet network bear no value. Only change if you know " "what you are doing." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:19 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:20 #: src/screens/NetworkSettings/helper.js:4 msgid "Updating custom network settings..." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:20 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:21 #: src/screens/NetworkSettings/helper.js:5 msgid "Network settings successfully customized." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:21 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:22 #: src/screens/NetworkSettings/helper.js:6 msgid "" "There was an error while customizing network settings. Please try again " "later." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:40 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:41 msgid "nodeUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:44 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:45 msgid "explorerUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:48 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:49 msgid "explorerServiceUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:52 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:53 msgid "txMiningServiceUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:202 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:62 +msgid "walletServiceUrl is required when walletServiceWsUrl is filled." +msgstr "" + +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:65 +msgid "walletServiceWsUrl is required when walletServiceUrl is filled." +msgstr "" + +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:229 msgid "Node URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:211 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:239 msgid "Explorer URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:220 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:248 msgid "Explorer Service URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:229 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:257 msgid "Transaction Mining Service URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:238 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:268 msgid "Wallet Service URL (optional)" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:247 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:277 msgid "Wallet Service WS URL (optional)" msgstr "" @@ -917,57 +925,57 @@ msgid "" "this can potentially make you susceptible to fraudulent schemes." msgstr "" -#: src/sagas/networkSettings.js:87 +#: src/sagas/networkSettings.js:88 msgid "Custom Network Settings cannot be empty." msgstr "" -#: src/sagas/networkSettings.js:94 +#: src/sagas/networkSettings.js:95 msgid "explorerUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:101 +#: src/sagas/networkSettings.js:102 msgid "explorerServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:108 +#: src/sagas/networkSettings.js:109 msgid "txMiningServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:115 +#: src/sagas/networkSettings.js:116 msgid "nodeUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:122 +#: src/sagas/networkSettings.js:123 msgid "walletServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:129 +#: src/sagas/networkSettings.js:130 msgid "walletServiceWsUrl should be a valid URL." msgstr "" #. If we fall into this situation, the app should be killed #. for the custom new network settings take effect. -#: src/sagas/networkSettings.js:288 +#: src/sagas/networkSettings.js:290 msgid "Wallet not found while trying to persist the custom network settings." msgstr "" -#: src/sagas/pushNotification.js:53 +#: src/sagas/pushNotification.js:59 msgid "Transaction" msgstr "Ingen transaktioner" -#: src/sagas/pushNotification.js:54 +#: src/sagas/pushNotification.js:60 msgid "Open" msgstr "Åben" -#: src/components/AskForPushNotification.js:21 +#: src/components/AskForPushNotification.js:22 msgid "Do you want to enable push notifications for this wallet?" msgstr "" -#: src/components/AskForPushNotification.js:22 +#: src/components/AskForPushNotification.js:23 msgid "You can always change this later in the settings menu" msgstr "" -#: src/components/AskForPushNotification.js:23 +#: src/components/AskForPushNotification.js:24 msgid "Yes, enable" msgstr "" diff --git a/locale/pt-br/texts.po b/locale/pt-br/texts.po index b76ce4767..3901f0f91 100644 --- a/locale/pt-br/texts.po +++ b/locale/pt-br/texts.po @@ -10,7 +10,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" -"X-Generator: Poedit 3.4\n" +"X-Generator: Poedit 3.3.1\n" #. This should never happen! #: src/models.js:24 @@ -112,20 +112,20 @@ msgstr "Há uma nova transação na sua carteira." msgid "New transaction received" msgstr "Nova transação recebida" -#: src/screens/About.js:82 +#: src/screens/About.js:83 msgid "ABOUT" msgstr "SOBRE" -#: src/screens/About.js:94 +#: src/screens/About.js:95 msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "" "Este aplicativo foi desenvolvido pela Hathor Labs e é distribuído de graça." -#: src/screens/About.js:98 src/screens/InitWallet.js:64 +#: src/screens/About.js:99 src/screens/InitWallet.js:64 msgid "This wallet is connected to the **mainnet**." msgstr "Esta wallet está conectada à **mainnet**." -#: src/screens/About.js:101 src/screens/InitWallet.js:67 +#: src/screens/About.js:102 src/screens/InitWallet.js:67 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as pocket " @@ -135,7 +135,7 @@ msgstr "" "Por isso, nós aconselhamos que você mantenha apenas uma pequena quantidade " "de tokens aqui." -#: src/screens/About.js:106 +#: src/screens/About.js:107 msgid "" "For further information, check out the |link1:Terms of Service| and |link2:" "Privacy Policy|, or our website |link3:https://hathor.network/|." @@ -214,30 +214,30 @@ msgstr "Os PINs estão diferentes. Tente novamente." msgid "Start the Wallet" msgstr "Iniciar a Wallet" -#: src/screens/CreateTokenAmount.js:119 src/screens/CreateTokenConfirm.js:163 +#: src/screens/CreateTokenAmount.js:120 src/screens/CreateTokenConfirm.js:163 #: src/screens/CreateTokenDepositNotice.js:42 src/screens/CreateTokenName.js:43 #: src/screens/CreateTokenSymbol.js:62 msgid "CREATE TOKEN" msgstr "CRIAR TOKEN" -#: src/screens/CreateTokenAmount.js:127 +#: src/screens/CreateTokenAmount.js:128 #, javascript-format msgid "Amount of ${ this.name } (${ this.symbol })" msgstr "Quantidade de ${ this.name } (${ this.symbol })" -#: src/screens/CreateTokenAmount.js:139 +#: src/screens/CreateTokenAmount.js:140 msgid "Deposit:" msgstr "Depósito:" -#: src/screens/CreateTokenAmount.js:143 +#: src/screens/CreateTokenAmount.js:144 #, javascript-format msgid "You have ${ amountAvailableText } HTR available" msgstr "Você tem ${ amountAvailableText } HTR disponíveis" -#: src/screens/CreateTokenAmount.js:148 src/screens/CreateTokenName.js:64 +#: src/screens/CreateTokenAmount.js:149 src/screens/CreateTokenName.js:64 #: src/screens/CreateTokenSymbol.js:84 src/screens/InitWallet.js:220 #: src/screens/InitWallet.js:341 src/screens/SendAddressInput.js:66 -#: src/screens/SendAmountInput.js:179 +#: src/screens/SendAmountInput.js:185 msgid "Next" msgstr "Próximo" @@ -436,30 +436,30 @@ msgstr "Palavras" msgid "Enter your seed words separated by space" msgstr "Digite suas palavras separadas por espaços" -#: src/screens/LoadHistoryScreen.js:67 +#: src/screens/LoadHistoryScreen.js:51 src/screens/LoadWalletErrorScreen.js:20 +msgid "Try again" +msgstr "Tente novamente" + +#: src/screens/LoadHistoryScreen.js:60 msgid "Loading your transactions" msgstr "Carregando suas transações" -#: src/screens/LoadHistoryScreen.js:70 +#: src/screens/LoadHistoryScreen.js:63 #, javascript-format -msgid "**${ _this.props.loadedData.transactions } transactions** found" -msgstr "**${ _this.props.loadedData.transactions } transações** encontradas" +msgid "**${ loadedData.transactions } transactions** found" +msgstr "**${ loadedData.transactions } transações** encontradas" -#: src/screens/LoadHistoryScreen.js:73 +#: src/screens/LoadHistoryScreen.js:66 #, javascript-format -msgid "**${ _this.props.loadedData.addresses } addresses** found" -msgstr "**${ _this.props.loadedData.addresses } endereços** encontrados" +msgid "**${ loadedData.addresses } addresses** found" +msgstr "**${ loadedData.addresses } endereços** encontrados" #: src/screens/LoadWalletErrorScreen.js:19 msgid "There's been an error connecting to the server." msgstr "Ocorreu um erro ao conectar com o servidor." -#: src/screens/LoadWalletErrorScreen.js:20 -msgid "Try again" -msgstr "Tente novamente" - #: src/screens/LoadWalletErrorScreen.js:21 src/screens/PinScreen.js:268 -#: src/screens/Settings.js:151 +#: src/screens/Settings.js:154 msgid "Reset wallet" msgstr "Resetar Wallet" @@ -543,7 +543,7 @@ msgstr "Desbloqueie sua Hathor Wallet" msgid "Cancel" msgstr "Cancelar" -#: src/screens/PushNotification.js:58 src/screens/Settings.js:125 +#: src/screens/PushNotification.js:58 src/screens/Settings.js:128 msgid "Push Notification" msgstr "Notificação" @@ -574,7 +574,7 @@ msgstr "" msgid "Payment Request" msgstr "Requisição de Pagamento" -#: src/screens/Receive.js:106 +#: src/screens/Receive.js:107 msgid "RECEIVE" msgstr "RECEBER" @@ -673,18 +673,18 @@ msgstr "ENVIAR" msgid "Address to send" msgstr "Endereço para enviar" -#: src/screens/SendAmountInput.js:105 +#: src/screens/SendAmountInput.js:110 msgid "Insufficient funds" msgstr "Saldo insuficiente" -#: src/screens/SendAmountInput.js:136 src/screens/SendConfirmScreen.js:125 +#: src/screens/SendAmountInput.js:141 src/screens/SendConfirmScreen.js:125 #, javascript-format msgid "${ amountAndToken } available" msgid_plural "${ amountAndToken } available" msgstr[0] "${ amountAndToken } disponível" msgstr[1] "${ amountAndToken } disponíveis" -#: src/screens/SendAmountInput.js:148 src/screens/SendConfirmScreen.js:133 +#: src/screens/SendAmountInput.js:154 src/screens/SendConfirmScreen.js:133 msgid "SEND ${ tokenNameUpperCase }" msgstr "ENVIAR ${ tokenNameUpperCase }" @@ -693,11 +693,11 @@ msgstr "ENVIAR ${ tokenNameUpperCase }" msgid "Your transfer is being processed" msgstr "Sua transferência está sendo processada" -#: src/sagas/helpers.js:132 src/screens/SendConfirmScreen.js:104 +#: src/sagas/helpers.js:136 src/screens/SendConfirmScreen.js:104 msgid "Enter your 6-digit pin to authorize operation" msgstr "Digite seu PIN de 6 dígitos para autorizar a operação" -#: src/sagas/helpers.js:133 src/screens/SendConfirmScreen.js:105 +#: src/sagas/helpers.js:137 src/screens/SendConfirmScreen.js:105 msgid "Authorize operation" msgstr "Autorizar operação" @@ -709,7 +709,7 @@ msgstr "Sua transferência de **${ this.amountAndToken }** foi confirmada" msgid "Address" msgstr "Endereço" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:258 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:288 #: src/screens/SendConfirmScreen.js:168 msgid "Send" msgstr "Enviar" @@ -732,43 +732,43 @@ msgstr "Você não tem o token requisitado [${ tokenLabel }]" msgid "Scan the QR code" msgstr "Leia o QR code" -#: src/screens/Settings.js:94 +#: src/screens/Settings.js:97 msgid "You are connected to" msgstr "Você está conectado à" -#: src/screens/Settings.js:102 +#: src/screens/Settings.js:105 msgid "General Settings" msgstr "Configurações Gerais" -#: src/screens/Settings.js:106 +#: src/screens/Settings.js:109 msgid "Connected to" msgstr "Conectado ao servidor" -#: src/screens/Settings.js:119 +#: src/screens/Settings.js:122 msgid "Security" msgstr "Segurança" -#: src/screens/Settings.js:132 +#: src/screens/Settings.js:135 msgid "Create a new token" msgstr "Criar um novo token" -#: src/screens/Settings.js:139 +#: src/screens/Settings.js:142 msgid "Register a token" msgstr "Registrar um token" -#: src/screens/Settings.js:155 +#: src/screens/Settings.js:158 msgid "About" msgstr "Sobre" -#: src/screens/Settings.js:162 +#: src/screens/Settings.js:165 msgid "Unique app identifier" msgstr "Identificador único do aplicativo" -#: src/screens/Settings.js:174 +#: src/screens/Settings.js:179 msgid "Developer Settings" msgstr "Configurações do Desenvolvedor" -#: src/screens/Settings.js:176 +#: src/screens/Settings.js:181 msgid "Network Settings" msgstr "Configurações de Rede" @@ -776,11 +776,11 @@ msgstr "Configurações de Rede" msgid "Unregister" msgstr "Desregistrar" -#: src/screens/UnregisterToken.js:112 +#: src/screens/UnregisterToken.js:108 msgid "UNREGISTER TOKEN" msgstr "DESREGISTRAR TOKEN" -#: src/screens/UnregisterToken.js:117 +#: src/screens/UnregisterToken.js:113 msgid "" "If you unregister this token **you won't be able to execute operations with " "it**, unless you register it again." @@ -788,19 +788,19 @@ msgstr "" "Se você desregistrar esse token **você não conseguirá mais executar " "operações com ele**, a não ser que você o registre novamente." -#: src/screens/UnregisterToken.js:120 +#: src/screens/UnregisterToken.js:116 msgid "" "You won't lose your tokens, they will just not appear on this wallet anymore." msgstr "" "Você não irá perder os seus tokens, eles apenas não irão mais aparecer nessa " "wallet." -#: src/screens/UnregisterToken.js:124 +#: src/screens/UnregisterToken.js:120 #, javascript-format msgid "I want to unregister the token **${ tokenLabel }**" msgstr "Eu quero desregistrar o token **${ tokenLabel }**" -#: src/screens/UnregisterToken.js:139 +#: src/screens/UnregisterToken.js:135 msgid "Unregister token" msgstr "Desregistrar token" @@ -850,11 +850,11 @@ msgstr "Conectar" msgid "Manual" msgstr "Digitar" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:17 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:18 msgid "Custom Network Settings" msgstr "Configurações de Rede Personalizada" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:18 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:19 msgid "" "Any token outside mainnet network bear no value. Only change if you know " "what you are doing." @@ -862,17 +862,17 @@ msgstr "" "Nenhum token fora da rede mainnet possui valor. Apenas mude a rede se você " "souber o que está fazendo." -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:19 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:20 #: src/screens/NetworkSettings/helper.js:4 msgid "Updating custom network settings..." msgstr "Atualizando configurações de rede..." -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:20 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:21 #: src/screens/NetworkSettings/helper.js:5 msgid "Network settings successfully customized." msgstr "Configuração de rede atualizada com sucesso." -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:21 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:22 #: src/screens/NetworkSettings/helper.js:6 msgid "" "There was an error while customizing network settings. Please try again " @@ -881,43 +881,51 @@ msgstr "" "Ocorreu um erro ao atualizar as configurações de rede. Por favor, tente " "novamente mais tarde." -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:40 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:41 msgid "nodeUrl is required." msgstr "nodeUrl é obrigatório." -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:44 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:45 msgid "explorerUrl is required." msgstr "explorerUrl é obrigatório." -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:48 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:49 msgid "explorerServiceUrl is required." msgstr "explorerServiceUrl é obrigatório." -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:52 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:53 msgid "txMiningServiceUrl is required." msgstr "txMiningServiceUrl é obrigatório." -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:202 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:62 +msgid "walletServiceUrl is required when walletServiceWsUrl is filled." +msgstr "walletServiceUrl é obrigatório quando walletServiceWsUrl está preenchido." + +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:65 +msgid "walletServiceWsUrl is required when walletServiceUrl is filled." +msgstr "walletServiceWsUrl é obrigatório quando walletServiceUrl está preenchido" + +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:229 msgid "Node URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:211 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:239 msgid "Explorer URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:220 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:248 msgid "Explorer Service URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:229 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:257 msgid "Transaction Mining Service URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:238 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:268 msgid "Wallet Service URL (optional)" msgstr "Wallet Service URL (opcional)" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:247 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:277 msgid "Wallet Service WS URL (optional)" msgstr "Wallet Service WS URL (opcional)" @@ -943,58 +951,58 @@ msgstr "" "circunstância altere a rede por sugestão de terceiros, uma vez que ao " "alterar a rede você pode ser vítima de fraude." -#: src/sagas/networkSettings.js:87 +#: src/sagas/networkSettings.js:88 msgid "Custom Network Settings cannot be empty." msgstr "As Configurações de Rede não podem estar vazias." -#: src/sagas/networkSettings.js:94 +#: src/sagas/networkSettings.js:95 msgid "explorerUrl should be a valid URL." msgstr "explorerUrl deve ser uma URL válida." -#: src/sagas/networkSettings.js:101 +#: src/sagas/networkSettings.js:102 msgid "explorerServiceUrl should be a valid URL." msgstr "explorerServiceUrl deve ser uma URL válida." -#: src/sagas/networkSettings.js:108 +#: src/sagas/networkSettings.js:109 msgid "txMiningServiceUrl should be a valid URL." msgstr "txMiningServiceUrl deve ser uma URL válida." -#: src/sagas/networkSettings.js:115 +#: src/sagas/networkSettings.js:116 msgid "nodeUrl should be a valid URL." msgstr "nodeUrl deve ser uma URL válida." -#: src/sagas/networkSettings.js:122 +#: src/sagas/networkSettings.js:123 msgid "walletServiceUrl should be a valid URL." msgstr "walletServiceUrl deve ser uma URL válida." -#: src/sagas/networkSettings.js:129 +#: src/sagas/networkSettings.js:130 msgid "walletServiceWsUrl should be a valid URL." msgstr "walletServiceWsUrl deve ser uma URL válida." #. If we fall into this situation, the app should be killed #. for the custom new network settings take effect. -#: src/sagas/networkSettings.js:288 +#: src/sagas/networkSettings.js:290 msgid "Wallet not found while trying to persist the custom network settings." msgstr "" "Wallet não encontrada ao persistir a configuração personalizada da rede." -#: src/sagas/pushNotification.js:53 +#: src/sagas/pushNotification.js:59 msgid "Transaction" msgstr "Transação" -#: src/sagas/pushNotification.js:54 +#: src/sagas/pushNotification.js:60 msgid "Open" msgstr "Abrir" -#: src/components/AskForPushNotification.js:21 +#: src/components/AskForPushNotification.js:22 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:22 +#: src/components/AskForPushNotification.js:23 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:23 +#: src/components/AskForPushNotification.js:24 msgid "Yes, enable" msgstr "Sim, habilitar" diff --git a/locale/ru-ru/texts.po b/locale/ru-ru/texts.po index 943fb348e..598ae15b2 100644 --- a/locale/ru-ru/texts.po +++ b/locale/ru-ru/texts.po @@ -11,7 +11,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" -"X-Generator: Poedit 3.2.2\n" +"X-Generator: Poedit 3.3.1\n" #. This should never happen! #: src/models.js:24 @@ -109,19 +109,19 @@ msgstr "" msgid "New transaction received" msgstr "Нет транзакций" -#: src/screens/About.js:82 +#: src/screens/About.js:83 msgid "ABOUT" msgstr "О НАС" -#: src/screens/About.js:94 +#: src/screens/About.js:95 msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "Это приложение разработано Hathor Labs и распространяется бесплатно." -#: src/screens/About.js:98 src/screens/InitWallet.js:64 +#: src/screens/About.js:99 src/screens/InitWallet.js:64 msgid "This wallet is connected to the **mainnet**." msgstr "Этот кошелек подключен к **mainnet**." -#: src/screens/About.js:101 src/screens/InitWallet.js:67 +#: src/screens/About.js:102 src/screens/InitWallet.js:67 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" "So, we advise you to keep only a small amount of tokens here, such as pocket " @@ -130,7 +130,7 @@ msgstr "" "Мобильный кошелек - не самый безопасный способ для хранения ваших токенов.\n" "Поэтому мы не советуем хранить на нем большие суммы." -#: src/screens/About.js:106 +#: src/screens/About.js:107 msgid "" "For further information, check out the |link1:Terms of Service| and |link2:" "Privacy Policy|, or our website |link3:https://hathor.network/|." @@ -207,30 +207,30 @@ msgstr "PIN-коды не совпадают. Попробуйте еще раз msgid "Start the Wallet" msgstr "Запустить кошелек" -#: src/screens/CreateTokenAmount.js:119 src/screens/CreateTokenConfirm.js:163 +#: src/screens/CreateTokenAmount.js:120 src/screens/CreateTokenConfirm.js:163 #: src/screens/CreateTokenDepositNotice.js:42 src/screens/CreateTokenName.js:43 #: src/screens/CreateTokenSymbol.js:62 msgid "CREATE TOKEN" msgstr "СОЗДАТЬ ТОКЕН" -#: src/screens/CreateTokenAmount.js:127 +#: src/screens/CreateTokenAmount.js:128 #, javascript-format msgid "Amount of ${ this.name } (${ this.symbol })" msgstr "Количество ${ this.name } (${ this.symbol })" -#: src/screens/CreateTokenAmount.js:139 +#: src/screens/CreateTokenAmount.js:140 msgid "Deposit:" msgstr "Депозит:" -#: src/screens/CreateTokenAmount.js:143 +#: src/screens/CreateTokenAmount.js:144 #, javascript-format msgid "You have ${ amountAvailableText } HTR available" msgstr "У вас ${ amountAvailableText } HTR" -#: src/screens/CreateTokenAmount.js:148 src/screens/CreateTokenName.js:64 +#: src/screens/CreateTokenAmount.js:149 src/screens/CreateTokenName.js:64 #: src/screens/CreateTokenSymbol.js:84 src/screens/InitWallet.js:220 #: src/screens/InitWallet.js:341 src/screens/SendAddressInput.js:66 -#: src/screens/SendAmountInput.js:179 +#: src/screens/SendAmountInput.js:185 msgid "Next" msgstr "Далее" @@ -426,30 +426,30 @@ msgstr "Слова" msgid "Enter your seed words separated by space" msgstr "Введите seed-фразу" -#: src/screens/LoadHistoryScreen.js:67 +#: src/screens/LoadHistoryScreen.js:51 src/screens/LoadWalletErrorScreen.js:20 +msgid "Try again" +msgstr "" + +#: src/screens/LoadHistoryScreen.js:60 msgid "Loading your transactions" msgstr "Загрузка ваших транзакций" -#: src/screens/LoadHistoryScreen.js:70 +#: src/screens/LoadHistoryScreen.js:63 #, javascript-format -msgid "**${ _this.props.loadedData.transactions } transactions** found" -msgstr "**${ _this.props.loadedData.transactions } транзакций** найдено" +msgid "**${ loadedData.transactions } transactions** found" +msgstr "**${ loadedData.transactions } транзакций** найдено" -#: src/screens/LoadHistoryScreen.js:73 +#: src/screens/LoadHistoryScreen.js:66 #, javascript-format -msgid "**${ _this.props.loadedData.addresses } addresses** found" -msgstr "**${ _this.props.loadedData.addresses } адресов** найдено" +msgid "**${ loadedData.addresses } addresses** found" +msgstr "**${ loadedData.addresses } адресов** найдено" #: src/screens/LoadWalletErrorScreen.js:19 msgid "There's been an error connecting to the server." msgstr "" -#: src/screens/LoadWalletErrorScreen.js:20 -msgid "Try again" -msgstr "" - #: src/screens/LoadWalletErrorScreen.js:21 src/screens/PinScreen.js:268 -#: src/screens/Settings.js:151 +#: src/screens/Settings.js:154 msgid "Reset wallet" msgstr "Сбросить кошелек" @@ -532,7 +532,7 @@ msgstr "Разблокировать Hathor Wallet" msgid "Cancel" msgstr "Отмена" -#: src/screens/PushNotification.js:58 src/screens/Settings.js:125 +#: src/screens/PushNotification.js:58 src/screens/Settings.js:128 msgid "Push Notification" msgstr "" @@ -561,7 +561,7 @@ msgstr "" msgid "Payment Request" msgstr "Запрос Средств" -#: src/screens/Receive.js:106 +#: src/screens/Receive.js:107 msgid "RECEIVE" msgstr "ПОЛУЧИТЬ" @@ -658,11 +658,11 @@ msgstr "ОТПРАВИТЬ" msgid "Address to send" msgstr "Адрес отправки" -#: src/screens/SendAmountInput.js:105 +#: src/screens/SendAmountInput.js:110 msgid "Insufficient funds" msgstr "Недостаточно средств" -#: src/screens/SendAmountInput.js:136 src/screens/SendConfirmScreen.js:125 +#: src/screens/SendAmountInput.js:141 src/screens/SendConfirmScreen.js:125 #, javascript-format msgid "${ amountAndToken } available" msgid_plural "${ amountAndToken } available" @@ -670,7 +670,7 @@ msgstr[0] "${ amountAndToken } доступно" msgstr[1] "${ amountAndToken } доступно" msgstr[2] "${ amountAndToken } доступно" -#: src/screens/SendAmountInput.js:148 src/screens/SendConfirmScreen.js:133 +#: src/screens/SendAmountInput.js:154 src/screens/SendConfirmScreen.js:133 msgid "SEND ${ tokenNameUpperCase }" msgstr "ОТПРАВИТЬ ${ tokenNameUpperCase }" @@ -679,11 +679,11 @@ msgstr "ОТПРАВИТЬ ${ tokenNameUpperCase }" msgid "Your transfer is being processed" msgstr "Ваш перевод обрабатывается" -#: src/sagas/helpers.js:132 src/screens/SendConfirmScreen.js:104 +#: src/sagas/helpers.js:136 src/screens/SendConfirmScreen.js:104 msgid "Enter your 6-digit pin to authorize operation" msgstr "Введите 6-значный PIN-код для авторизации операции" -#: src/sagas/helpers.js:133 src/screens/SendConfirmScreen.js:105 +#: src/sagas/helpers.js:137 src/screens/SendConfirmScreen.js:105 msgid "Authorize operation" msgstr "Авторизовать операцию" @@ -695,7 +695,7 @@ msgstr "Ваш перевод **${ this.amountAndToken }** был подтвер msgid "Address" msgstr "Адрес" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:258 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:288 #: src/screens/SendConfirmScreen.js:168 msgid "Send" msgstr "Отправить" @@ -718,43 +718,43 @@ msgstr "У вас нет запрошенного токена [${ tokenLabel }] msgid "Scan the QR code" msgstr "Сканировать QR-код" -#: src/screens/Settings.js:94 +#: src/screens/Settings.js:97 msgid "You are connected to" msgstr "Вы подключены к" -#: src/screens/Settings.js:102 +#: src/screens/Settings.js:105 msgid "General Settings" msgstr "" -#: src/screens/Settings.js:106 +#: src/screens/Settings.js:109 msgid "Connected to" msgstr "Подключены к" -#: src/screens/Settings.js:119 +#: src/screens/Settings.js:122 msgid "Security" msgstr "Безопасность" -#: src/screens/Settings.js:132 +#: src/screens/Settings.js:135 msgid "Create a new token" msgstr "Создать новый токен" -#: src/screens/Settings.js:139 +#: src/screens/Settings.js:142 msgid "Register a token" msgstr "Зарегистрировать токен" -#: src/screens/Settings.js:155 +#: src/screens/Settings.js:158 msgid "About" msgstr "О нас" -#: src/screens/Settings.js:162 +#: src/screens/Settings.js:165 msgid "Unique app identifier" msgstr "" -#: src/screens/Settings.js:174 +#: src/screens/Settings.js:179 msgid "Developer Settings" msgstr "" -#: src/screens/Settings.js:176 +#: src/screens/Settings.js:181 msgid "Network Settings" msgstr "" @@ -762,11 +762,11 @@ msgstr "" msgid "Unregister" msgstr "Отменить регистрацию" -#: src/screens/UnregisterToken.js:112 +#: src/screens/UnregisterToken.js:108 msgid "UNREGISTER TOKEN" msgstr "ОТМЕНИТЬ РЕГИСТРАЦИЮ ТОКЕНА" -#: src/screens/UnregisterToken.js:117 +#: src/screens/UnregisterToken.js:113 msgid "" "If you unregister this token **you won't be able to execute operations with " "it**, unless you register it again." @@ -774,19 +774,19 @@ msgstr "" "Если вы отмените регистрацию этого токена, **вы не сможете выполнять " "операции с ним**, пока не зарегистрируете его снова." -#: src/screens/UnregisterToken.js:120 +#: src/screens/UnregisterToken.js:116 msgid "" "You won't lose your tokens, they will just not appear on this wallet anymore." msgstr "" "Вы не потеряете свои токены, они просто больше не будут появляться на этом " "кошельке." -#: src/screens/UnregisterToken.js:124 +#: src/screens/UnregisterToken.js:120 #, javascript-format msgid "I want to unregister the token **${ tokenLabel }**" msgstr "Я хочу отменить регистрацию токена **${ tokenLabel }**" -#: src/screens/UnregisterToken.js:139 +#: src/screens/UnregisterToken.js:135 msgid "Unregister token" msgstr "Отменить регистрацию токена" @@ -835,70 +835,78 @@ msgstr "" msgid "Manual" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:17 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:18 msgid "Custom Network Settings" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:18 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:19 msgid "" "Any token outside mainnet network bear no value. Only change if you know " "what you are doing." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:19 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:20 #: src/screens/NetworkSettings/helper.js:4 msgid "Updating custom network settings..." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:20 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:21 #: src/screens/NetworkSettings/helper.js:5 msgid "Network settings successfully customized." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:21 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:22 #: src/screens/NetworkSettings/helper.js:6 msgid "" "There was an error while customizing network settings. Please try again " "later." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:40 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:41 msgid "nodeUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:44 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:45 msgid "explorerUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:48 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:49 msgid "explorerServiceUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:52 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:53 msgid "txMiningServiceUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:202 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:62 +msgid "walletServiceUrl is required when walletServiceWsUrl is filled." +msgstr "" + +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:65 +msgid "walletServiceWsUrl is required when walletServiceUrl is filled." +msgstr "" + +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:229 msgid "Node URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:211 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:239 msgid "Explorer URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:220 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:248 msgid "Explorer Service URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:229 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:257 msgid "Transaction Mining Service URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:238 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:268 msgid "Wallet Service URL (optional)" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:247 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:277 msgid "Wallet Service WS URL (optional)" msgstr "" @@ -921,57 +929,57 @@ msgid "" "this can potentially make you susceptible to fraudulent schemes." msgstr "" -#: src/sagas/networkSettings.js:87 +#: src/sagas/networkSettings.js:88 msgid "Custom Network Settings cannot be empty." msgstr "" -#: src/sagas/networkSettings.js:94 +#: src/sagas/networkSettings.js:95 msgid "explorerUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:101 +#: src/sagas/networkSettings.js:102 msgid "explorerServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:108 +#: src/sagas/networkSettings.js:109 msgid "txMiningServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:115 +#: src/sagas/networkSettings.js:116 msgid "nodeUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:122 +#: src/sagas/networkSettings.js:123 msgid "walletServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:129 +#: src/sagas/networkSettings.js:130 msgid "walletServiceWsUrl should be a valid URL." msgstr "" #. If we fall into this situation, the app should be killed #. for the custom new network settings take effect. -#: src/sagas/networkSettings.js:288 +#: src/sagas/networkSettings.js:290 msgid "Wallet not found while trying to persist the custom network settings." msgstr "" -#: src/sagas/pushNotification.js:53 +#: src/sagas/pushNotification.js:59 msgid "Transaction" msgstr "Нет транзакций" -#: src/sagas/pushNotification.js:54 +#: src/sagas/pushNotification.js:60 msgid "Open" msgstr "Открыть" -#: src/components/AskForPushNotification.js:21 +#: src/components/AskForPushNotification.js:22 msgid "Do you want to enable push notifications for this wallet?" msgstr "" -#: src/components/AskForPushNotification.js:22 +#: src/components/AskForPushNotification.js:23 msgid "You can always change this later in the settings menu" msgstr "" -#: src/components/AskForPushNotification.js:23 +#: src/components/AskForPushNotification.js:24 msgid "Yes, enable" msgstr "" diff --git a/locale/texts.pot b/locale/texts.pot index c88c574bd..497ab9564 100644 --- a/locale/texts.pot +++ b/locale/texts.pot @@ -98,20 +98,20 @@ msgstr "" msgid "New transaction received" msgstr "" -#: src/screens/About.js:82 +#: src/screens/About.js:83 msgid "ABOUT" msgstr "" -#: src/screens/About.js:94 +#: src/screens/About.js:95 msgid "This app is developed by Hathor Labs and is distributed for free." msgstr "" -#: src/screens/About.js:98 +#: src/screens/About.js:99 #: src/screens/InitWallet.js:64 msgid "This wallet is connected to the **mainnet**." msgstr "" -#: src/screens/About.js:101 +#: src/screens/About.js:102 #: src/screens/InitWallet.js:67 msgid "" "A mobile wallet is not the safest place to store your tokens.\n" @@ -119,7 +119,7 @@ msgid "" "pocket money." msgstr "" -#: src/screens/About.js:106 +#: src/screens/About.js:107 msgid "" "For further information, check out the |link1:Terms of Service| and " "|link2:Privacy Policy|, or our website |link3:https://hathor.network/|." @@ -196,7 +196,7 @@ msgstr "" msgid "Start the Wallet" msgstr "" -#: src/screens/CreateTokenAmount.js:119 +#: src/screens/CreateTokenAmount.js:120 #: src/screens/CreateTokenConfirm.js:163 #: src/screens/CreateTokenDepositNotice.js:42 #: src/screens/CreateTokenName.js:43 @@ -204,27 +204,27 @@ msgstr "" msgid "CREATE TOKEN" msgstr "" -#: src/screens/CreateTokenAmount.js:127 +#: src/screens/CreateTokenAmount.js:128 #, javascript-format msgid "Amount of ${ this.name } (${ this.symbol })" msgstr "" -#: src/screens/CreateTokenAmount.js:139 +#: src/screens/CreateTokenAmount.js:140 msgid "Deposit:" msgstr "" -#: src/screens/CreateTokenAmount.js:143 +#: src/screens/CreateTokenAmount.js:144 #, javascript-format msgid "You have ${ amountAvailableText } HTR available" msgstr "" -#: src/screens/CreateTokenAmount.js:148 +#: src/screens/CreateTokenAmount.js:149 #: src/screens/CreateTokenName.js:64 #: src/screens/CreateTokenSymbol.js:84 #: src/screens/InitWallet.js:220 #: src/screens/InitWallet.js:341 #: src/screens/SendAddressInput.js:66 -#: src/screens/SendAmountInput.js:179 +#: src/screens/SendAmountInput.js:185 msgid "Next" msgstr "" @@ -412,31 +412,32 @@ msgstr "" msgid "Enter your seed words separated by space" msgstr "" -#: src/screens/LoadHistoryScreen.js:67 +#: src/screens/LoadHistoryScreen.js:51 +#: src/screens/LoadWalletErrorScreen.js:20 +msgid "Try again" +msgstr "" + +#: src/screens/LoadHistoryScreen.js:60 msgid "Loading your transactions" msgstr "" -#: src/screens/LoadHistoryScreen.js:70 +#: src/screens/LoadHistoryScreen.js:63 #, javascript-format -msgid "**${ _this.props.loadedData.transactions } transactions** found" +msgid "**${ loadedData.transactions } transactions** found" msgstr "" -#: src/screens/LoadHistoryScreen.js:73 +#: src/screens/LoadHistoryScreen.js:66 #, javascript-format -msgid "**${ _this.props.loadedData.addresses } addresses** found" +msgid "**${ loadedData.addresses } addresses** found" msgstr "" #: src/screens/LoadWalletErrorScreen.js:19 msgid "There's been an error connecting to the server." msgstr "" -#: src/screens/LoadWalletErrorScreen.js:20 -msgid "Try again" -msgstr "" - #: src/screens/LoadWalletErrorScreen.js:21 #: src/screens/PinScreen.js:268 -#: src/screens/Settings.js:151 +#: src/screens/Settings.js:154 msgid "Reset wallet" msgstr "" @@ -523,7 +524,7 @@ msgid "Cancel" msgstr "" #: src/screens/PushNotification.js:58 -#: src/screens/Settings.js:125 +#: src/screens/Settings.js:128 msgid "Push Notification" msgstr "" @@ -552,7 +553,7 @@ msgstr "" msgid "Payment Request" msgstr "" -#: src/screens/Receive.js:106 +#: src/screens/Receive.js:107 msgid "RECEIVE" msgstr "" @@ -648,11 +649,11 @@ msgstr "" msgid "Address to send" msgstr "" -#: src/screens/SendAmountInput.js:105 +#: src/screens/SendAmountInput.js:110 msgid "Insufficient funds" msgstr "" -#: src/screens/SendAmountInput.js:136 +#: src/screens/SendAmountInput.js:141 #: src/screens/SendConfirmScreen.js:125 #, javascript-format msgid "${ amountAndToken } available" @@ -660,7 +661,7 @@ msgid_plural "${ amountAndToken } available" msgstr[0] "" msgstr[1] "" -#: src/screens/SendAmountInput.js:148 +#: src/screens/SendAmountInput.js:154 #: src/screens/SendConfirmScreen.js:133 msgid "SEND ${ tokenNameUpperCase }" msgstr "" @@ -670,12 +671,12 @@ msgstr "" msgid "Your transfer is being processed" msgstr "" -#: src/sagas/helpers.js:132 +#: src/sagas/helpers.js:136 #: src/screens/SendConfirmScreen.js:104 msgid "Enter your 6-digit pin to authorize operation" msgstr "" -#: src/sagas/helpers.js:133 +#: src/sagas/helpers.js:137 #: src/screens/SendConfirmScreen.js:105 msgid "Authorize operation" msgstr "" @@ -688,7 +689,7 @@ msgstr "" msgid "Address" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:258 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:288 #: src/screens/SendConfirmScreen.js:168 msgid "Send" msgstr "" @@ -711,43 +712,43 @@ msgstr "" msgid "Scan the QR code" msgstr "" -#: src/screens/Settings.js:94 +#: src/screens/Settings.js:97 msgid "You are connected to" msgstr "" -#: src/screens/Settings.js:102 +#: src/screens/Settings.js:105 msgid "General Settings" msgstr "" -#: src/screens/Settings.js:106 +#: src/screens/Settings.js:109 msgid "Connected to" msgstr "" -#: src/screens/Settings.js:119 +#: src/screens/Settings.js:122 msgid "Security" msgstr "" -#: src/screens/Settings.js:132 +#: src/screens/Settings.js:135 msgid "Create a new token" msgstr "" -#: src/screens/Settings.js:139 +#: src/screens/Settings.js:142 msgid "Register a token" msgstr "" -#: src/screens/Settings.js:155 +#: src/screens/Settings.js:158 msgid "About" msgstr "" -#: src/screens/Settings.js:162 +#: src/screens/Settings.js:165 msgid "Unique app identifier" msgstr "" -#: src/screens/Settings.js:174 +#: src/screens/Settings.js:179 msgid "Developer Settings" msgstr "" -#: src/screens/Settings.js:176 +#: src/screens/Settings.js:181 msgid "Network Settings" msgstr "" @@ -755,28 +756,28 @@ msgstr "" msgid "Unregister" msgstr "" -#: src/screens/UnregisterToken.js:112 +#: src/screens/UnregisterToken.js:108 msgid "UNREGISTER TOKEN" msgstr "" -#: src/screens/UnregisterToken.js:117 +#: src/screens/UnregisterToken.js:113 msgid "" "If you unregister this token **you won't be able to execute operations with " "it**, unless you register it again." msgstr "" -#: src/screens/UnregisterToken.js:120 +#: src/screens/UnregisterToken.js:116 msgid "" "You won't lose your tokens, they will just not appear on this wallet " "anymore." msgstr "" -#: src/screens/UnregisterToken.js:124 +#: src/screens/UnregisterToken.js:120 #, javascript-format msgid "I want to unregister the token **${ tokenLabel }**" msgstr "" -#: src/screens/UnregisterToken.js:139 +#: src/screens/UnregisterToken.js:135 msgid "Unregister token" msgstr "" @@ -825,70 +826,78 @@ msgstr "" msgid "Manual" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:17 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:18 msgid "Custom Network Settings" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:18 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:19 msgid "" "Any token outside mainnet network bear no value. Only change if you know " "what you are doing." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:19 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:20 #: src/screens/NetworkSettings/helper.js:4 msgid "Updating custom network settings..." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:20 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:21 #: src/screens/NetworkSettings/helper.js:5 msgid "Network settings successfully customized." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:21 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:22 #: src/screens/NetworkSettings/helper.js:6 msgid "" "There was an error while customizing network settings. Please try again " "later." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:40 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:41 msgid "nodeUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:44 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:45 msgid "explorerUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:48 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:49 msgid "explorerServiceUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:52 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:53 msgid "txMiningServiceUrl is required." msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:202 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:62 +msgid "walletServiceUrl is required when walletServiceWsUrl is filled." +msgstr "" + +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:65 +msgid "walletServiceWsUrl is required when walletServiceUrl is filled." +msgstr "" + +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:229 msgid "Node URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:211 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:239 msgid "Explorer URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:220 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:248 msgid "Explorer Service URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:229 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:257 msgid "Transaction Mining Service URL" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:238 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:268 msgid "Wallet Service URL (optional)" msgstr "" -#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:247 +#: src/screens/NetworkSettings/CustomNetworkSettingsScreen.js:277 msgid "Wallet Service WS URL (optional)" msgstr "" @@ -911,57 +920,57 @@ msgid "" "this can potentially make you susceptible to fraudulent schemes." msgstr "" -#: src/sagas/networkSettings.js:87 +#: src/sagas/networkSettings.js:88 msgid "Custom Network Settings cannot be empty." msgstr "" -#: src/sagas/networkSettings.js:94 +#: src/sagas/networkSettings.js:95 msgid "explorerUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:101 +#: src/sagas/networkSettings.js:102 msgid "explorerServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:108 +#: src/sagas/networkSettings.js:109 msgid "txMiningServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:115 +#: src/sagas/networkSettings.js:116 msgid "nodeUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:122 +#: src/sagas/networkSettings.js:123 msgid "walletServiceUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:129 +#: src/sagas/networkSettings.js:130 msgid "walletServiceWsUrl should be a valid URL." msgstr "" -#: src/sagas/networkSettings.js:288 +#: src/sagas/networkSettings.js:290 #. If we fall into this situation, the app should be killed #. for the custom new network settings take effect. msgid "Wallet not found while trying to persist the custom network settings." msgstr "" -#: src/sagas/pushNotification.js:53 +#: src/sagas/pushNotification.js:59 msgid "Transaction" msgstr "" -#: src/sagas/pushNotification.js:54 +#: src/sagas/pushNotification.js:60 msgid "Open" msgstr "" -#: src/components/AskForPushNotification.js:21 +#: src/components/AskForPushNotification.js:22 msgid "Do you want to enable push notifications for this wallet?" msgstr "" -#: src/components/AskForPushNotification.js:22 +#: src/components/AskForPushNotification.js:23 msgid "You can always change this later in the settings menu" msgstr "" -#: src/components/AskForPushNotification.js:23 +#: src/components/AskForPushNotification.js:24 msgid "Yes, enable" msgstr "" diff --git a/package-lock.json b/package-lock.json index 78a9c79ff..1e72e4509 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "HathorMobile", - "version": "0.26.1", + "version": "0.27.0-rc.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "HathorMobile", - "version": "0.26.1", + "version": "0.27.0-rc.6", "hasInstallScript": true, "dependencies": { "@fortawesome/fontawesome-svg-core": "1.2.36", "@fortawesome/free-regular-svg-icons": "6.4.0", "@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/react-native-fontawesome": "0.2.7", + "@hathor/unleash-client": "0.1.0", "@hathor/wallet-lib": "1.0.1", "@notifee/react-native": "5.7.0", "@react-native-async-storage/async-storage": "1.19.0", @@ -2540,6 +2541,14 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@hathor/unleash-client": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@hathor/unleash-client/-/unleash-client-0.1.0.tgz", + "integrity": "sha512-SR1JBQkegKMLNhU5yWYjHcZVC9EZ9kkDz/X5a2RHZsr+dhMic1oriqin3S8jjvIhmjn/uBZFlvzaTm7ll7h3mw==", + "engines": { + "node": ">=18" + } + }, "node_modules/@hathor/wallet-lib": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@hathor/wallet-lib/-/wallet-lib-1.0.1.tgz", diff --git a/package.json b/package.json index 019c133e5..ba569c195 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "HathorMobile", - "version": "0.26.1", + "version": "0.27.0-rc.6", "engines": { "node": ">=18.0.0", "npm": ">=9.0.0" @@ -21,6 +21,7 @@ "@fortawesome/free-regular-svg-icons": "6.4.0", "@fortawesome/free-solid-svg-icons": "6.4.0", "@fortawesome/react-native-fontawesome": "0.2.7", + "@hathor/unleash-client": "0.1.0", "@hathor/wallet-lib": "1.0.1", "@notifee/react-native": "5.7.0", "@react-native-async-storage/async-storage": "1.19.0", diff --git a/pre_release.sh b/pre_release.sh new file mode 100755 index 000000000..5c22eb3fd --- /dev/null +++ b/pre_release.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Script to release new versions for iOS and Android. After running it, you still +# have to build the project on XCode and Android Studio. +# +# This script expects a file `./env` with exported envvars. +# + +set -e # Exit on any command failure. +set -u # Exit on unset variables. +set -v # Verbose mode for better debugging + +rm -rf node_modules/ + +rm -f ./android/app/google-services.json +rm -f ./android/app/GoogleService-Info.plist +rm -f ./notifications/GoogleService-Info.plist + +npm ci + +(cd ios/ && pod install) + +make i18n + +source ./env +mkdir -p ./notifications +aws s3 cp ${HATHOR_WALLET_MOBILE_S3_PATH}/google-services.json ./android/app +aws s3 cp ${HATHOR_WALLET_MOBILE_S3_PATH}/GoogleService-Info.plist ./notifications/ diff --git a/src/App.js b/src/App.js index e89eea26b..29deff761 100644 --- a/src/App.js +++ b/src/App.js @@ -534,7 +534,7 @@ class _AppStackWrapper extends React.Component { if (!this.props.wallet?.storage) { return; } - const tokens = [...INITIAL_TOKENS]; + const tokens = { ...INITIAL_TOKENS }; const iterator = this.props.wallet.storage.getRegisteredTokens(); let next = await iterator.next(); // XXX: The "for await" syntax wouldbe better but this is failing due to @@ -542,11 +542,7 @@ class _AppStackWrapper extends React.Component { while (!next.done) { const token = next.value; // We need to filter the token data to remove the metadata from this list (e.g. balance) - tokens.push({ - uid: token.uid, - symbol: token.symbol, - name: token.name, - }); + tokens[token.uid] = { ...token }; // eslint-disable-next-line no-await-in-loop next = await iterator.next(); } diff --git a/src/actions.js b/src/actions.js index 379d9360a..f188dd893 100644 --- a/src/actions.js +++ b/src/actions.js @@ -135,7 +135,7 @@ export const types = { NETWORKSETTINGS_UPDATE_INVALID: 'NETWORKSETTINGS_UPDATE_INVALID', /* It indicates the update request has failed. */ NETWORKSETTINGS_UPDATE_FAILURE: 'NETWORK_SETTINGS_UPDATE_FAILURE', - /* It updates the redux state of network settings status */ + /* It updates the redux state of network settings status. */ NETWORKSETTINGS_UPDATE_READY: 'NETWORK_SETTINGS_UPDATE_READY', }; @@ -252,7 +252,7 @@ export const updateSelectedToken = (selectedToken) => ( export const newToken = (token) => ({ type: types.NEW_TOKEN, payload: token }); /** - * tokens {Array} list of tokens to update state + * @param {Object.} tokens map of tokens to update state */ export const setTokens = (tokens) => ({ type: types.SET_TOKENS, payload: tokens }); @@ -913,8 +913,8 @@ export const networkSettingsUpdateState = (customNetwork) => ({ * network: string, * nodeUrl: string, * explorerUrl: string, - * txMiningServiceUrl: string, * explorerServiceUrl: string, + * txMiningServiceUrl: string, * walletServiceUrl?: string * walletServiceWsUrl?: string * }} customNetwork Settings to persist diff --git a/src/components/AskForPushNotification.js b/src/components/AskForPushNotification.js index 1b88e7982..f1a468d89 100644 --- a/src/components/AskForPushNotification.js +++ b/src/components/AskForPushNotification.js @@ -13,16 +13,23 @@ export default function AskForPushNotification(props) { dispatch(pushDismissOptInQuestion()); }; - if (!showOptIn) { - return null; - } - return ( + const putDismissOptInQuestion = () => { + dispatch(pushDismissOptInQuestion()); + }; + + const pushNotificationOptInModal = () => ( onEnablePushNotifications()} - onDismiss={() => dispatch(pushDismissOptInQuestion())} + onAction={onEnablePushNotifications} + onDismiss={putDismissOptInQuestion} /> ); + + if (showOptIn) { + return pushNotificationOptInModal(); + } + + return null; } diff --git a/src/components/TokenSelect.js b/src/components/TokenSelect.js index 8596af203..d3d151a01 100644 --- a/src/components/TokenSelect.js +++ b/src/components/TokenSelect.js @@ -30,12 +30,13 @@ import { COLORS } from '../styles/themes'; * @param {Record} props.tokensBalance * @param {{ uid: string }} props.selectedToken * @param {unknown} props.tokenMetadata - * @param {unknown} props.tokens + * @param {{ [uid: string]: { uid: string; name: string; symbol: string; }}} props.tokens * @param {unknown} props.header * @param {boolean} props.renderArrow * @param {function} props.onItemPress */ const TokenSelect = (props) => { + const tokens = Object.values(props.tokens); const renderItem = ({ item, index }) => { const symbolWrapperStyle = [styles.symbolWrapper]; const symbolTextStyle = [styles.text, styles.leftText, styles.symbolText]; @@ -95,7 +96,7 @@ const TokenSelect = (props) => { {props.header} } Map of tokens added plus initial tokens, + * @see {@link INITIAL_TOKENS} + */ tokens: INITIAL_TOKENS, + /** + * selectedToken {{ + * uid: string; + * name; string; + * symbol: string + * }} Token selected to operate with + * @example + * { + * name: 'YanCoin', + * symbol: 'YAN', + * uid: '000003a3b261e142d3dfd84970d3a50a93b5bc3a66a3b6ba973956148a3eb824' + * } + */ selectedToken: DEFAULT_TOKEN, isOnline: false, serverInfo: { version: '', network: '' }, @@ -211,6 +227,18 @@ const initialState = { featureToggles: { ...FEATURE_TOGGLE_DEFAULTS, }, + /** + * @param {{ + * stage: string; + * network: string; + * nodeUrl: string; + * explorerUrl: string; + * explorerServiceUrl: string; + * txMiningServiceUrl: string; + * walletServiceUrl: string; + * walletServiceWsUrl: string; + * }} + */ networkSettings: PRE_SETTINGS_MAINNET, networkSettingsInvalid: {}, networkSettingsStatus: NETWORKSETTINGS_STATUS.READY, @@ -499,24 +527,29 @@ const onUpdateSelectedToken = (state, action) => ({ /** * Add a new token to the list of available tokens in this wallet + * @param {object} state + * @param {{payload: { uid: string }}} action containing a token data in payload, + * @see {@link initialState.tokens} */ -const onNewToken = (state, action) => ({ +const onNewToken = (state, { payload }) => ({ ...state, - tokens: [...state.tokens, action.payload], + tokens: { ...state.tokens, [payload.uid]: { ...payload } }, }); /** * Set the list of tokens added in this wallet + * @param {object} state + * @param {{ payload }} action containing all registered tokens in payload */ -const onSetTokens = (state, action) => { +const onSetTokens = (state, { payload }) => { let { selectedToken } = state; - if (action.payload.indexOf(selectedToken) === -1) { + if (payload[selectedToken.uid] == null) { // We have unregistered this token selectedToken = DEFAULT_TOKEN; } return { ...state, - tokens: [...action.payload], + tokens: { ...payload }, selectedToken, }; }; diff --git a/src/sagas/errorHandler.js b/src/sagas/errorHandler.js index 277f53cae..1958d1243 100644 --- a/src/sagas/errorHandler.js +++ b/src/sagas/errorHandler.js @@ -60,7 +60,15 @@ export function* errorModalHandler(action) { if (reportError) { const wallet = yield select((state) => state.wallet); - const accessData = yield call(() => wallet.getAccessData().catch(() => null)); + + let accessData = null; + try { + accessData = yield call(() => wallet.getAccessData()); + } catch (_e) { + // Nothing to do, the error might have ocurred before the wallet is + // started. + } + sentryReportError(error, accessData); } diff --git a/src/sagas/featureToggle.js b/src/sagas/featureToggle.js index 688887d8b..9300bf984 100644 --- a/src/sagas/featureToggle.js +++ b/src/sagas/featureToggle.js @@ -7,29 +7,25 @@ import { Platform } from 'react-native'; import VersionNumber from 'react-native-version-number'; -import { UnleashClient, EVENTS as UnleashEvents } from 'unleash-proxy-client'; +import UnleashClient, { FetchTogglesStatus } from '@hathor/unleash-client'; import { get } from 'lodash'; import { - takeEvery, all, call, delay, put, - cancelled, select, - race, - take, fork, spawn, + takeEvery, } from 'redux-saga/effects'; -import { eventChannel } from 'redux-saga'; import { getUniqueId } from 'react-native-device-info'; import { + types, setUnleashClient, setFeatureToggles, featureToggleInitialized, - types, } from '../actions'; import { UNLEASH_URL, @@ -40,7 +36,6 @@ import { } from '../constants'; import { disableFeaturesIfNeeded } from './helpers'; -const CONNECT_TIMEOUT = 10000; const MAX_RETRIES = 5; export function* handleInitFailed(currentRetry) { @@ -71,9 +66,10 @@ export function* fetchTogglesRoutine() { const unleashClient = yield select((state) => state.unleashClient); try { - // This call always make unleash to emit the event 'UPDATE', - // which by its turn triggers the action 'FEATURE_TOGGLE_UPDATE' - yield call(() => unleashClient.fetchToggles()); + const state = yield call(() => unleashClient.fetchToggles()); + if (state === FetchTogglesStatus.Updated) { + yield put({ type: types.FEATURE_TOGGLE_UPDATE }); + } } catch (e) { // No need to do anything here as it will try again automatically in // UNLEASH_POLLING_INTERVAL. Just prevent it from crashing the saga. @@ -82,15 +78,19 @@ export function* fetchTogglesRoutine() { } } -export function* monitorFeatureFlags(currentRetry = 0) { - const unleashClient = new UnleashClient({ - url: UNLEASH_URL, - clientKey: UNLEASH_CLIENT_KEY, - refreshInterval: -1, - disableRefresh: true, // Disable it, we will handle it ourselves - appName: `wallet-mobile-${Platform.OS}`, - }); +export function* handleToggleUpdate() { + console.log('Handling feature toggle update'); + const unleashClient = yield select((state) => state.unleashClient); + const networkSettings = yield select((state) => state.networkSettings); + const toggles = unleashClient.getToggles(); + const featureToggles = disableFeaturesIfNeeded(networkSettings, mapFeatureToggles(toggles)); + + yield put(setFeatureToggles(featureToggles)); + yield put({ type: types.FEATURE_TOGGLE_UPDATED }); +} + +export function* monitorFeatureFlags(currentRetry = 0) { const { appVersion } = VersionNumber; const options = { @@ -102,40 +102,30 @@ export function* monitorFeatureFlags(currentRetry = 0) { }, }; + const unleashClient = new UnleashClient({ + url: UNLEASH_URL, + clientKey: UNLEASH_CLIENT_KEY, + refreshInterval: -1, + disableRefresh: true, // Disable it, we will handle it ourselves + appName: `wallet-mobile-${Platform.OS}`, + context: options, + }); + try { - yield call(() => unleashClient.updateContext(options)); yield put(setUnleashClient(unleashClient)); - // Listeners should be set before unleashClient.start so we don't miss - // updates - yield fork(setupUnleashListeners, unleashClient); - - // Start without awaiting it so we can listen for the - // READY event - unleashClient.start(); - - const { error, timeout } = yield race({ - error: take(types.FEATURE_TOGGLE_ERROR), - success: take(types.FEATURE_TOGGLE_READY), - timeout: delay(CONNECT_TIMEOUT), - }); - - if (error || timeout) { - throw new Error('Error or timeout while connecting to unleash proxy.'); - } + yield call(() => unleashClient.fetchToggles()); // Fork the routine to download toggles. yield fork(fetchTogglesRoutine); - // At this point, unleashClient.start() already fetched the toggles - const featureToggles = mapFeatureToggles(unleashClient.toggles); + // At this point, unleashClient.fetchToggles() already fetched the toggles + // (this will throw if it hasn't) + const featureToggles = mapFeatureToggles(unleashClient.getToggles()); yield put(setFeatureToggles(featureToggles)); yield put(featureToggleInitialized()); } catch (e) { - console.error('Error initializing unleash'); - unleashClient.stop(); - yield put(setUnleashClient(null)); // Wait 500ms before retrying @@ -143,45 +133,6 @@ export function* monitorFeatureFlags(currentRetry = 0) { // Spawn so it's detached from the current thread yield spawn(handleInitFailed, currentRetry); - } finally { - if (yield cancelled()) { - yield call(() => unleashClient.stop()); - } - } -} - -export function* setupUnleashListeners(unleashClient) { - const channel = eventChannel((emitter) => { - const l1 = () => emitter({ type: types.FEATURE_TOGGLE_UPDATE }); - const l2 = () => emitter({ type: types.FEATURE_TOGGLE_READY }); - const l3 = (err) => emitter({ type: types.FEATURE_TOGGLE_ERROR, data: err }); - - unleashClient.on(UnleashEvents.UPDATE, l1); - unleashClient.on(UnleashEvents.READY, l2); - unleashClient.on(UnleashEvents.ERROR, l3); - - return () => { - // XXX: This should be a cleanup but removeListener does not exist - // This will throw an error and it will interfere with other sagas - // Since it works without the cleanup i will leave this method empty - // until have determined the best cleanup approach - }; - }); - - try { - while (true) { - const message = yield take(channel); - - yield put({ - type: message.type, - payload: message.data, - }); - } - } finally { - if (yield cancelled()) { - // When we close the channel, it will remove the event listener - channel.close(); - } } } @@ -198,22 +149,6 @@ function mapFeatureToggles(toggles) { }, {}); } -export function* handleToggleUpdate() { - const unleashClient = yield select((state) => state.unleashClient); - const featureTogglesInitialized = yield select((state) => state.featureTogglesInitialized); - const networkSettings = yield select((state) => state.networkSettings); - - if (!unleashClient || !featureTogglesInitialized) { - return; - } - - const { toggles } = unleashClient; - const featureToggles = disableFeaturesIfNeeded(networkSettings, mapFeatureToggles(toggles)); - - yield put(setFeatureToggles(featureToggles)); - yield put({ type: types.FEATURE_TOGGLE_UPDATED }); -} - export function* saga() { yield all([ fork(monitorFeatureFlags), diff --git a/src/sagas/helpers.js b/src/sagas/helpers.js index 042e1b9d1..6a77228e4 100644 --- a/src/sagas/helpers.js +++ b/src/sagas/helpers.js @@ -13,6 +13,7 @@ import { take, call, select, + delay, } from 'redux-saga/effects'; import { t } from 'ttag'; import axiosWrapperCreateRequestInstance from '@hathor/wallet-lib/lib/api/axiosWrapper'; @@ -28,6 +29,9 @@ import { WALLET_SERVICE_FEATURE_TOGGLE, WALLET_SERVICE_REQUEST_TIMEOUT, networkSettingsKeyMap, + MAX_RETRIES, + INITIAL_RETRY_LATENCY, + LATENCY_MULTIPLIER, } from '../constants'; import { STORE } from '../store'; @@ -153,11 +157,17 @@ export function isUnlockScreen(action) { * Get registered tokens from the wallet instance. * @param {HathorWallet} wallet * @param {boolean} excludeHTR If we should exclude the HTR token. - * @returns {Promise<{ uid: string, symbol: string, name: string }[]>} + * @returns {Promise<{ + * [uid: string]: { + * uid: string; + * symbol: string; + * name: string; + * } + * }>} */ export async function getRegisteredTokens(wallet, excludeHTR = false) { const htrUid = hathorLib.constants.HATHOR_TOKEN_CONFIG.uid; - const tokens = []; + const tokens = {}; // redux-saga generator magic does not work well with the "for await..of" syntax // The asyncGenerator is not recognized as an iterable and it throws an exception @@ -167,11 +177,7 @@ export async function getRegisteredTokens(wallet, excludeHTR = false) { while (!next.done) { const token = next.value; if ((!excludeHTR) || token.uid !== htrUid) { - tokens.push({ - uid: token.uid, - symbol: token.symbol, - name: token.name, - }); + tokens[token.uid] = { ...token }; } // eslint-disable-next-line no-await-in-loop next = await iterator.next(); @@ -179,12 +185,21 @@ export async function getRegisteredTokens(wallet, excludeHTR = false) { // XXX: This will add any default tokens configured, not only HTR if (!excludeHTR) { - tokens.unshift(...INITIAL_TOKENS); + return { ...INITIAL_TOKENS, ...tokens }; } return tokens; } +/** + * Flat registered tokens to uid. + * @param {{ tokens: Object }} Map of registered tokens by uid + * @returns {string[]} Array of token uid + */ +export function getRegisteredTokenUids({ tokens }) { + return Object.keys(tokens); +} + /** * Check if a token is registered in the context of the saga functions. * @param {HathorWallet} wallet @@ -193,7 +208,7 @@ export async function getRegisteredTokens(wallet, excludeHTR = false) { */ export async function isTokenRegistered(wallet, tokenUid) { const tokens = await getRegisteredTokens(wallet); - return tokens.some((token) => token.uid === tokenUid); + return tokens[tokenUid] != null; } export async function getFullnodeNetwork() { @@ -264,3 +279,53 @@ export function getNetworkSettings(state) { // has precedence, once it indicates a custom network. return STORE.getItem(networkSettingsKeyMap.networkSettings) ?? state.networkSettings; } + +/** + * A request abstraction that applies a progressive retry strategy. + * One can define how many retries it should make or use the default value. + * + * @param {Promise} request The async callback function to be executed. + * @param {number} maxRetries The max retries allowed, with default value. + * Notice this param should be at least 1 to make sense. + * @returns {any} A success object from the request. + * @throws An error after retries exhausted. + * + * @example + * yield call(progressiveRetryRequest, async () => asyncFn()); + * // use default maxRetries + * + * @example + * yield call(progressiveRetryRequest, async () => asyncFn(), 3); + * // use custom maxRetries equal to 3 + */ +export function* progressiveRetryRequest(request, maxRetries = MAX_RETRIES) { + let lastError = null; + + // eslint-disable-next-line no-plusplus + for (let i = 0; i <= maxRetries; i++) { + try { + // return if success + return yield call(request) + } catch (error) { + lastError = error; + } + + // skip delay for last call + if (i === maxRetries) { + continue; + } + + // attempt 0: 300ms + // attempt 1: 330ms + // attempt 2: 420ms + // attempt 3: 570ms + // attempt 4: 780ms + // attempt 5: 1050ms + // attempt 6: 1380ms + // attempt 7: 1770ms + yield delay(INITIAL_RETRY_LATENCY + LATENCY_MULTIPLIER * (i * i)); + } + + // throw last error after retries exhausted + throw lastError; +} diff --git a/src/sagas/networkSettings.js b/src/sagas/networkSettings.js index eb445b835..f1af235f1 100644 --- a/src/sagas/networkSettings.js +++ b/src/sagas/networkSettings.js @@ -6,7 +6,6 @@ import { networkSettingsPersistStore, networkSettingsUpdateInvalid, networkSettingsUpdateFailure, - networkSettingsUpdateState, networkSettingsUpdateSuccess, networkSettingsUpdateWaiting, types, @@ -36,11 +35,6 @@ import { isWalletServiceEnabled } from './wallet'; * Initialize the network settings saga when the wallet starts successfully. */ export function* initNetworkSettings() { - const customNetwork = STORE.getItem(networkSettingsKeyMap.networkSettings); - if (customNetwork) { - yield put(networkSettingsUpdateState(customNetwork)); - } - const status = yield select((state) => state.networkSettingsStatus); if (status === NETWORKSETTINGS_STATUS.WAITING) { // This branch completes the network update by delivering @@ -60,13 +54,14 @@ export function* initNetworkSettings() { * * @param {{ * payload: { - * stage: string, - * network: string, - * nodeUrl: string, - * explorerUrl: string, - * explorerServiceUrl: string, - * walletServiceUrl?: string - * walletServiceWsUrl?: string + * stage: string; + * network: string; + * nodeUrl: string; + * explorerUrl: string; + * explorerServiceUrl: string; + * txMiningServiceUrl: string; + * walletServiceUrl?: string; + * walletServiceWsUrl?: string; * } * }} action contains the payload with the new * network settings requested by the user to be processd. @@ -148,15 +143,15 @@ export function* updateNetworkSettings(action) { txMiningServiceUrl: networkSettings.txMiningServiceUrl, }; + config.setTxMiningUrl(txMiningServiceUrl); config.setExplorerServiceBaseUrl(explorerServiceUrl); config.setServerUrl(nodeUrl); - config.setTxMiningUrl(txMiningServiceUrl); // - walletServiceUrl has precedence // - nodeUrl as fallback let potentialNetwork; let network; - if (walletServiceUrl && useWalletService) { + if (useWalletService && !isEmpty(walletServiceUrl)) { config.setWalletServiceBaseUrl(walletServiceUrl); config.setWalletServiceBaseWsUrl(walletServiceWsUrl); @@ -222,6 +217,7 @@ export function* updateNetworkSettings(action) { nodeUrl, explorerUrl, explorerServiceUrl, + txMiningServiceUrl, walletServiceUrl, walletServiceWsUrl, }; diff --git a/src/sagas/pushNotification.js b/src/sagas/pushNotification.js index ff71565d0..09793ed30 100644 --- a/src/sagas/pushNotification.js +++ b/src/sagas/pushNotification.js @@ -15,6 +15,8 @@ import { take, takeLatest, debounce, + spawn, + delay, } from 'redux-saga/effects'; import messaging from '@react-native-firebase/messaging'; import notifee from '@notifee/react-native'; @@ -314,12 +316,27 @@ export function* init() { } } - // If the user has not been asked yet, we should ask him if he wants to enable push notifications - // We should appear only once, so we should save the fact that the user has dismissed the question - const optInDismissed = STORE.getItem(pushNotificationKey.optInDismissed); - if (optInDismissed === null || !optInDismissed) { - yield put(pushAskOptInQuestion()); - } + // Gives users the option to opt-in the Push Notification + // after its initialization, but only opens the opt-in + // modal after wallet become ready. + // Spwan creates a detached thread from this current thread. + yield spawn(function* handleOptIn() { + const { ready } = yield race({ + ready: take(types.WALLET_STATE_READY), + // Do nothing with error, only terminates the thread. + error: take(types.WALLET_STATE_ERROR), + }); + + if (ready) { + // Ask user for push notification opt-in if it has not + // been asked previously. It appears only once because + // we persist the dismiss action on store. + const optInDismissed = STORE.getItem(pushNotificationKey.optInDismissed); + if (optInDismissed === null || !optInDismissed) { + yield put(pushAskOptInQuestion()); + } + } + }); } /** @@ -358,6 +375,11 @@ export function* loadWallet() { const network = new Network(networkSettings.network); const pin = yield call(showPinScreenForResult, dispatch); + + // Delay 300ms to resume script execution in the next loop. + // This solution liberates the PinScreen to dismiss. + yield delay(300); + const seed = yield STORE.getWalletWords(pin); walletService = new HathorWalletServiceWallet({ seed, @@ -372,6 +394,7 @@ export function* loadWallet() { }); } catch (error) { yield put(pushLoadWalletFailed({ error })); + return; } } else { walletService = yield select((state) => state.wallet); @@ -386,19 +409,19 @@ export function* loadWallet() { */ const hasPostNotificationAuthorization = async () => { let status = await messaging().hasPermission(); - if (status === messaging.AuthorizationStatus.BLOCKED) { + if (status === messaging.AuthorizationStatus.DENIED) { log.debug('Device not authorized to send push notification and blocked to ask permission.'); return false; } - if (status === messaging.AuthorizationStatus.NOT_DETERMINED) { - log.debug('Device clean. Asking for permission to send push notification.'); + if (status === messaging.AuthorizationStatus.NOT_DETERMINED + || status === messaging.AuthorizationStatus.EPHEMERAL + || status === messaging.AuthorizationStatus.PROVISIONAL) { + log.debug('Asking for permission to send push notification.'); status = await messaging().requestPermission(); } - log.debug('Device permission status: ', status); - return status === messaging.AuthorizationStatus.AUTHORIZED - || status === messaging.AuthorizationStatus.PROVISIONAL; + return status === messaging.AuthorizationStatus.AUTHORIZED; }; /** @@ -563,7 +586,7 @@ export const getTxDetails = async (wallet, txId) => { name: each.tokenName, symbol: each.tokenSymbol, balance: each.balance, - isRegistered: await isTokenRegistered(each.tokenId), + isRegistered: await isTokenRegistered(wallet, each.tokenId), }))), }); diff --git a/src/sagas/tokens.js b/src/sagas/tokens.js index 0c236d268..c777b74d8 100644 --- a/src/sagas/tokens.js +++ b/src/sagas/tokens.js @@ -18,7 +18,7 @@ import { import { metadataApi } from '@hathor/wallet-lib'; import { channel } from 'redux-saga'; import { get } from 'lodash'; -import { specificTypeAndPayload, dispatchAndWait } from './helpers'; +import { specificTypeAndPayload, dispatchAndWait, getRegisteredTokenUids } from './helpers'; import { mapTokenHistory } from '../utils'; import { types, @@ -139,6 +139,7 @@ function* fetchTokenHistory(action) { * This saga will route the actions dispatched from SET_TOKEN and NEW_TOKEN to the * TOKEN_FETCH_BALANCE_REQUESTED saga, the idea is to load the balance for new tokens * registered or created on the app. + * @param {{type: string; payload: Object;}} action to route */ function* routeTokenChange(action) { const wallet = yield select((state) => state.wallet); @@ -153,8 +154,8 @@ function* routeTokenChange(action) { break; case 'SET_TOKENS': default: - for (const token of action.payload) { - yield put({ type: types.TOKEN_FETCH_BALANCE_REQUESTED, tokenId: token.uid }); + for (const uid of getRegisteredTokenUids({ tokens: action.payload })) { + yield put({ type: types.TOKEN_FETCH_BALANCE_REQUESTED, tokenId: uid }); } break; } @@ -242,7 +243,7 @@ export function* fetchTokenMetadata({ tokenId }) { } } -export function* fetchTokenData(tokenId) { +export function* fetchTokenData(tokenId, force = false) { const fetchBalanceResponse = yield call( dispatchAndWait, tokenFetchBalanceRequested(tokenId), @@ -255,7 +256,7 @@ export function* fetchTokenData(tokenId) { ); const fetchHistoryResponse = yield call( dispatchAndWait, - tokenFetchHistoryRequested(tokenId), + tokenFetchHistoryRequested(tokenId, force), specificTypeAndPayload(types.TOKEN_FETCH_HISTORY_SUCCESS, { tokenId, }), diff --git a/src/sagas/wallet.js b/src/sagas/wallet.js index db28b6cdc..05c20b0f0 100644 --- a/src/sagas/wallet.js +++ b/src/sagas/wallet.js @@ -12,6 +12,7 @@ import { Network, constants as hathorLibConstants, config, + errors, } from '@hathor/wallet-lib'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { @@ -30,11 +31,12 @@ import { } from 'redux-saga/effects'; import { eventChannel } from 'redux-saga'; import { getUniqueId } from 'react-native-device-info'; -import { get } from 'lodash'; +import { get, isEmpty } from 'lodash'; import { DEFAULT_TOKEN, WALLET_SERVICE_FEATURE_TOGGLE, PUSH_NOTIFICATION_FEATURE_TOGGLE, + networkSettingsKeyMap, } from '../constants'; import { STORE } from '../store'; import { @@ -61,6 +63,8 @@ import { setAvailablePushNotification, resetWalletSuccess, setTokens, + onExceptionCaptured, + networkSettingsUpdateState, } from '../actions'; import { fetchTokenData } from './tokens'; import { @@ -70,6 +74,8 @@ import { checkForFeatureFlag, getRegisteredTokens, getNetworkSettings, + getRegisteredTokenUids, + progressiveRetryRequest, } from './helpers'; import { setKeychainPin } from '../utils'; @@ -81,6 +87,7 @@ export const WALLET_STATUS = { }; export const IGNORE_WS_TOGGLE_FLAG = 'featureFlags:ignoreWalletServiceFlag'; +export const EXPIRE_WS_IGNORE_FLAG = 24 * 60 * 60 * 1000; // 24 hours /** * Returns the value of the PUSH_NOTIFICATION_FEATURE_TOGGLE feature flag @@ -97,15 +104,35 @@ export function* isPushNotificationEnabled() { * @returns {Generator} */ export function* isWalletServiceEnabled() { + // Users might have had issues with the wallet-service in the past, we can detect + // old flags because they were booleans, new flags are integers (timestamps) const shouldIgnoreFlag = yield call(() => AsyncStorage.getItem(IGNORE_WS_TOGGLE_FLAG)); + const shouldIgnoreFlagTs = parseInt(shouldIgnoreFlag, 10); - // If we should ignore flag, it shouldn't matter what the featureToggle is, wallet service - // is definitely disabled. - if (shouldIgnoreFlag) { - return false; + if (!Number.isNaN(shouldIgnoreFlagTs)) { + const now = new Date().getTime(); + const delta = now - shouldIgnoreFlagTs; + + if (delta < EXPIRE_WS_IGNORE_FLAG) { + console.log(`Still ignoring wallet-service, will expire in ${EXPIRE_WS_IGNORE_FLAG - delta}ms`); + return false; + } + } else { + // We can safely remove the old flag and continue + yield call(() => AsyncStorage.removeItem(IGNORE_WS_TOGGLE_FLAG)); } - const walletServiceEnabled = yield call(checkForFeatureFlag, WALLET_SERVICE_FEATURE_TOGGLE); + let walletServiceEnabled = yield call(checkForFeatureFlag, WALLET_SERVICE_FEATURE_TOGGLE); + + // At this point, the networkSettings have already been set by startWallet. + const networkSettings = yield select(getNetworkSettings); + if (walletServiceEnabled && isEmpty(networkSettings.walletServiceUrl)) { + // In case of an empty value for walletServiceUrl, it means the user + // doesn't intend to use the Wallet Service. Therefore, we need to force + // a disable on it. + walletServiceEnabled = false; + yield put(setUseWalletService(false)); + } return walletServiceEnabled; } @@ -116,6 +143,35 @@ export function* startWallet(action) { pin, } = action.payload; + // clean memory storage and metadata before starting the wallet. + // This should be cleaned when stopping the wallet, + // but the wallet may be closed unexpectedly + const storage = STORE.getStorage(); + yield call([storage.store, storage.store.cleanMetadata]); // clean metadata on memory + yield call([storage, storage.cleanStorage], true); // clean transaction history + + // As this is a core setting for the wallet, it should be loaded first. + // Network settings either from store or redux state + let networkSettings; + // Custom network settings are persisted in the app storage + const customNetwork = STORE.getItem(networkSettingsKeyMap.networkSettings); + if (customNetwork) { + networkSettings = customNetwork; + // On custom network settings one may use a different + // URL for the services from the ones registered by default + // for mainnet and testnet in the lib, and the wallet must + // behave consistently to the URLs set + config.setExplorerServiceBaseUrl(networkSettings.explorerServiceUrl); + config.setServerUrl(networkSettings.nodeUrl); + config.setTxMiningUrl(networkSettings.txMiningServiceUrl); + + // If the wallet is initialized from quit state it must + // update the network settings on redux state + yield put(networkSettingsUpdateState(networkSettings)); + } else { + networkSettings = yield select(getNetworkSettings); + } + const uniqueDeviceId = getUniqueId(); const useWalletService = yield call(isWalletServiceEnabled); const usePushNotification = yield call(isPushNotificationEnabled); @@ -123,23 +179,14 @@ export function* startWallet(action) { yield put(setUseWalletService(useWalletService)); yield put(setAvailablePushNotification(usePushNotification)); - // clean storage and metadata before starting the wallet - // this should be cleaned when stopping the wallet, - // but the wallet may be closed unexpectedly - const storage = STORE.getStorage(); - yield storage.store.cleanMetadata(); - yield storage.cleanStorage(true); - // This is a work-around so we can dispatch actions from inside callbacks. let dispatch; yield put((_dispatch) => { dispatch = _dispatch; }); - const networkSettings = yield select(getNetworkSettings); - let wallet; - if (useWalletService) { + if (useWalletService && !isEmpty(networkSettings.walletServiceUrl)) { const network = new Network(networkSettings.network); // Set urls for wallet service @@ -193,20 +240,28 @@ export function* startWallet(action) { password: pin, }); } catch (e) { + // WalletRequestError can either be a network error making the request + // fail or the wallet might have failed to start and returned status: error. + // We don't need to send those to Sentry, so we'll capture all the others + // here: + if (!(e instanceof errors.WalletRequestError)) { + yield put(onExceptionCaptured(e, false)); + } + if (useWalletService) { // Wallet Service start wallet will fail if the status returned from // the service is 'error' or if the start wallet request failed. + // // We should fallback to the old facade by storing the flag to ignore // the feature flag - yield call(() => AsyncStorage.setItem(IGNORE_WS_TOGGLE_FLAG, 'true')); - - // Yield the same action so it will now load on the old facade - yield put(action); - } else { - console.log('failed to start fullnode wallet'); - yield put(startWalletFailed()); - return; + // + // This might be a temporary issue on the wallet-service side, we should + // store the timestamp of when this flag was set, so we're able to expire it + yield call(() => AsyncStorage.setItem(IGNORE_WS_TOGGLE_FLAG, `${new Date().getTime()}`)); } + + yield put(startWalletFailed()); + return; } setKeychainPin(pin); @@ -233,6 +288,7 @@ export function* startWallet(action) { yield call(loadTokens); } catch (e) { console.error('Tokens load failed: ', e); + yield put(onExceptionCaptured(e, false)); yield put(startWalletFailed()); return; } @@ -264,9 +320,10 @@ export function* startWallet(action) { /** * This saga will load both HTR and DEFAULT_TOKEN (if they are different) - * and dispatch actions to asynchronously load all registered tokens. + * and dispatch actions to asynchronously load all registered tokens forcefully. * * Will throw an error if the download fails for any token. + * @returns {string[]} Array of token uid */ export function* loadTokens() { const customTokenUid = DEFAULT_TOKEN.uid; @@ -274,19 +331,22 @@ export function* loadTokens() { // fetchTokenData will throw an error if the download failed, we should just // let it crash as throwing an error is the default behavior for loadTokens - yield call(fetchTokenData, htrUid); + yield call(fetchTokenData, htrUid, true); if (customTokenUid !== htrUid) { + // custom tokens doesn't need to be forced to download because its history status + // will be marked as invalidated, and history will get requested the next time a user + // enters the history screen. yield call(fetchTokenData, customTokenUid); } const wallet = yield select((state) => state.wallet); - const registeredTokens = yield getRegisteredTokens(wallet); + const tokens = yield getRegisteredTokens(wallet); - yield put(setTokens(registeredTokens)); + yield put(setTokens(tokens)); - const registeredUids = registeredTokens.map((t) => t.uid); + const registeredUids = getRegisteredTokenUids({ tokens }); // We don't need to wait for the metadatas response, so we can just // spawn a new "thread" to handle it. @@ -429,8 +489,7 @@ export function* handleTx(action) { } // find tokens affected by the transaction - const stateTokens = yield select((state) => state.tokens); - const registeredTokens = stateTokens.map((token) => token.uid); + const registeredUids = yield select(getRegisteredTokenUids); // To be able to only download balances for tokens belonging to this wallet, we // need a list of tokens and addresses involved in the transaction from both the @@ -445,26 +504,34 @@ export function* handleTx(action) { return acc; } - const { token, decoded: { address } } = io; + const { token: tokenUid, decoded: { address } } = io; // We are only interested in registered tokens - if (registeredTokens.indexOf(token) === -1) { + if (registeredUids.indexOf(tokenUid) === -1) { return acc; } - if (!acc[0][token]) { - acc[0][token] = new Set([]); + if (!acc[0][tokenUid]) { + acc[0][tokenUid] = new Set([]); } - acc[0][token].add(address); + acc[0][tokenUid].add(address); acc[1].add(address); return acc; }, [{}, new Set([])],); - const txWalletAddresses = yield call(wallet.checkAddressesMine.bind(wallet), [...txAddresses]); - const tokensToDownload = []; + let txWalletAddresses = null; + try { + const request = async () => wallet.checkAddressesMine.bind(wallet)([...txAddresses]); + txWalletAddresses = yield call(progressiveRetryRequest, request); + } catch (error) { + // Emmit a fatal error feedback to user and halts tx processing. + yield put(onExceptionCaptured(error, true)); + return; + } + const tokensToDownload = []; for (const [tokenUid, addresses] of Object.entries(tokenAddressesMap)) { for (const [address] of addresses.entries()) { // txWalletAddresses should always have the address we requested, but we should double check @@ -605,6 +672,7 @@ export function* onWalletReloadData() { } try { + // Here we force the download of tokens history const registeredTokens = yield call(loadTokens); const customTokenUid = DEFAULT_TOKEN.uid; @@ -636,6 +704,8 @@ export function* onWalletReloadData() { // Finally, set the wallet to READY by dispatching startWalletSuccess yield put(startWalletSuccess()); } catch (e) { + console.log('Wallet reload data failed: ', e); + yield put(onExceptionCaptured(e, false)); yield put(startWalletFailed()); } } diff --git a/src/screens/LoadHistoryScreen.js b/src/screens/LoadHistoryScreen.js index 1bac0fee0..9bccf50c6 100644 --- a/src/screens/LoadHistoryScreen.js +++ b/src/screens/LoadHistoryScreen.js @@ -5,11 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { StyleSheet, Text, View, } from 'react-native'; -import { connect } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { t } from 'ttag'; import { @@ -21,66 +21,58 @@ import Spinner from '../components/Spinner'; import TextFmt from '../components/TextFmt'; import { COLORS } from '../styles/themes'; -/** - * loadHistoryStatus {Object} progress on loading tx history { - * active {boolean} indicates we're loading the tx history - * error {boolean} error loading history - * } - */ -const mapStateToProps = (state) => ({ - loadHistoryStatus: state.loadHistoryStatus, - loadedData: state.loadedData, -}); +export default function LoadHistoryScreen() { + const dispatch = useDispatch(); + /** + * loadHistoryStatus {Object} progress on loading tx history { + * active {boolean} indicates we're loading the tx history + * error {boolean} error loading history + * } + */ + const loadHistoryStatus = useSelector((state) => state.loadHistoryStatus); + const loadedData = useSelector((state) => state.loadedData); -const mapDispatchToProps = (dispatch) => ({ - reloadHistory: () => dispatch(onWalletReload()), - resetLoadedData: () => dispatch(resetLoadedData()), -}); - -class LoadHistoryScreen extends React.Component { - componentDidMount() { - this.props.resetLoadedData(); - } + useEffect(() => { + dispatch(resetLoadedData()); + }, []); - render() { - const renderError = () => ( - - - There's been an error connecting to the server - - this.props.reloadHistory()} - title='Try again' - /> - - ); + const renderError = () => ( + + + There's been an error connecting to the server + + dispatch(onWalletReload())} + title={t`Try again`} + /> + + ); - const renderLoading = () => ( - - - - {t`Loading your transactions`} - - - {t`**${this.props.loadedData.transactions} transactions** found`} - - - {t`**${this.props.loadedData.addresses} addresses** found`} - - - ); + const renderLoading = () => ( + + + + {t`Loading your transactions`} + + + {t`**${loadedData.transactions} transactions** found`} + + + {t`**${loadedData.addresses} addresses** found`} + + + ); - return ( - - {this.props.loadHistoryStatus.error ? renderError() : renderLoading()} - - ); - } + return ( + + {loadHistoryStatus.error ? renderError() : renderLoading()} + + ); } const styles = StyleSheet.create({ @@ -91,5 +83,3 @@ const styles = StyleSheet.create({ marginTop: 16, }, }); - -export default connect(mapStateToProps, mapDispatchToProps)(LoadHistoryScreen); diff --git a/src/screens/NetworkSettings/CustomNetworkSettingsScreen.js b/src/screens/NetworkSettings/CustomNetworkSettingsScreen.js index 3eaf44166..6572028f5 100644 --- a/src/screens/NetworkSettings/CustomNetworkSettingsScreen.js +++ b/src/screens/NetworkSettings/CustomNetworkSettingsScreen.js @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { View, Text, StyleSheet, Image } from 'react-native'; +import { View, Text, StyleSheet, Image, ScrollView, KeyboardAvoidingView } from 'react-native'; import { useDispatch, useSelector } from 'react-redux'; import { t } from 'ttag'; import { isEmpty } from 'lodash'; @@ -11,14 +11,14 @@ import SimpleInput from '../../components/SimpleInput'; import errorIcon from '../../assets/images/icErrorBig.png'; import checkIcon from '../../assets/images/icCheckBig.png'; import Spinner from '../../components/Spinner'; -import { hasSucceed, hasFailed, isLoading } from './helper'; +import { hasSucceeded, hasFailed, isLoading } from './helper'; import { AlertUI } from '../../styles/themes'; import { WALLET_SERVICE_FEATURE_TOGGLE } from '../../constants'; const customNetworkSettingsTitleText = t`Custom Network Settings`.toUpperCase(); const warningText = t`Any token outside mainnet network bear no value. Only change if you know what you are doing.`; const feedbackLoadingText = t`Updating custom network settings...`; -const feedbackSucceedText = t`Network settings successfully customized.`; +const feedbackSucceededText = t`Network settings successfully customized.`; const feedbackFailedText = t`There was an error while customizing network settings. Please try again later.`; /** @@ -53,26 +53,44 @@ function validate(formModel) { invalidModel.txMiningServiceUrl = t`txMiningServiceUrl is required.`; } + // if any wallet-service fields have a value, then evaluate, otherwise pass + if (formModel.walletServiceUrl || formModel.walletServiceWsUrl) { + // if not both wallet-service fields have a value, then evaluate, otherwise pass + if (!(formModel.walletServiceUrl && formModel.walletServiceWsUrl)) { + // invalidade the one that don't have a value + if (!formModel.walletServiceUrl) { + invalidModel.walletServiceUrl = t`walletServiceUrl is required when walletServiceWsUrl is filled.`; + } + if (!formModel.walletServiceWsUrl) { + invalidModel.walletServiceWsUrl = t`walletServiceWsUrl is required when walletServiceUrl is filled.`; + } + } + } + return invalidModel; } const styles = StyleSheet.create({ - container: { + wrapper: { flex: 1, }, - content: { + keyboardWrapper: { flex: 1, - padding: 16, - paddingBottom: 48, + }, + container: { + paddingHorizontal: 16, }, feedbackModalIcon: { height: 105, width: 105 }, - warningContainer: { + warningWrapper: { + paddingVertical: 16, + }, + warningCard: { + flexShrink: 1, borderRadius: 8, backgroundColor: AlertUI.lightColor, - marginBottom: 32, borderWidth: 1, borderColor: AlertUI.baseHslColor.addLightness(4).toString(), }, @@ -81,13 +99,12 @@ const styles = StyleSheet.create({ color: AlertUI.darkColor, padding: 12, }, + formWrapper: { + paddingBottom: 16, + }, input: { marginBottom: 24, }, - buttonContainer: { - alignSelf: 'stretch', - marginTop: 'auto', - }, }); export const CustomNetworkSettingsNav = Symbol('CustomNetworkSettings').toString(); @@ -104,18 +121,18 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => { nodeUrl: networkSettings.nodeUrl, explorerUrl: networkSettings.explorerUrl, explorerServiceUrl: networkSettings.explorerServiceUrl, + txMiningServiceUrl: networkSettings.txMiningServiceUrl || '', walletServiceUrl: networkSettings.walletServiceUrl || '', walletServiceWsUrl: networkSettings.walletServiceWsUrl || '', - txMiningServiceUrl: networkSettings.txMiningServiceUrl || '', }); const [invalidModel, setInvalidModel] = useState({ nodeUrl: networkSettingsInvalid?.nodeUrl || '', explorerUrl: networkSettingsInvalid?.explorerUrl || '', explorerServiceUrl: networkSettingsInvalid?.explorerServiceUrl || '', + txMiningServiceUrl: networkSettingsInvalid.txMiningServiceUrl || '', walletServiceUrl: networkSettingsInvalid?.walletServiceUrl || '', walletServiceWsUrl: networkSettingsInvalid?.walletServiceWsUrl || '', - txMiningServiceUrl: networkSettingsInvalid.txMiningServiceUrl || '', }); // eslint-disable-next-line max-len @@ -167,104 +184,115 @@ export const CustomNetworkSettingsScreen = ({ navigation }) => { }, []); return ( - - navigation.goBack()} - /> - - {isLoading(networkSettingsStatus) && ( - } - text={feedbackLoadingText} + + + navigation.goBack()} /> - )} - {hasSucceed(networkSettingsStatus) && ( - )} - text={feedbackSucceedText} - onDismiss={handleFeedbackModalDismiss} - /> - )} + {isLoading(networkSettingsStatus) && ( + } + text={feedbackLoadingText} + /> + )} - {hasFailed(networkSettingsStatus) && ( - )} - text={feedbackFailedText} - onDismiss={handleFeedbackModalDismiss} - /> - )} + {hasSucceeded(networkSettingsStatus) && ( + )} + text={feedbackSucceededText} + onDismiss={handleFeedbackModalDismiss} + /> + )} - - - {warningText} - - + {hasFailed(networkSettingsStatus) && ( + )} + text={feedbackFailedText} + onDismiss={handleFeedbackModalDismiss} + /> + )} - + + + {warningText} + + - + + + - + - {walletServiceEnabled && ( - <> + - - )} - - - + {walletServiceEnabled && ( + <> + + + + + )} + + + + - + ); }; diff --git a/src/screens/NetworkSettings/NetworkPreSettingsScreen.js b/src/screens/NetworkSettings/NetworkPreSettingsScreen.js index f87064a22..0d10617bd 100644 --- a/src/screens/NetworkSettings/NetworkPreSettingsScreen.js +++ b/src/screens/NetworkSettings/NetworkPreSettingsScreen.js @@ -21,7 +21,7 @@ import FeedbackModal from '../../components/FeedbackModal'; import { networkSettingsPersistStore, networkSettingsUpdateReady } from '../../actions'; import { PRE_SETTINGS_MAINNET, PRE_SETTINGS_TESTNET } from '../../constants'; import { CustomNetworkSettingsNav } from './CustomNetworkSettingsScreen'; -import { feedbackSucceedText, feedbackFailedText, feedbackLoadingText, hasFailed, isLoading, hasSucceed } from './helper'; +import { feedbackSucceedText, feedbackFailedText, feedbackLoadingText, hasFailed, isLoading, hasSucceeded } from './helper'; import errorIcon from '../../assets/images/icErrorBig.png'; import checkIcon from '../../assets/images/icCheckBig.png'; @@ -102,7 +102,7 @@ export function NetworkPreSettingsScreen({ navigation }) { /> )} - {hasSucceed(networkSettingsStatus) && ( + {hasSucceeded(networkSettingsStatus) && ( )} text={feedbackSucceedText} diff --git a/src/screens/NetworkSettings/helper.js b/src/screens/NetworkSettings/helper.js index 11b7ffd45..187ad08be 100644 --- a/src/screens/NetworkSettings/helper.js +++ b/src/screens/NetworkSettings/helper.js @@ -10,7 +10,7 @@ export const feedbackFailedText = t`There was an error while customizing network * @param {object} networkSettingsStatus - status from redux store * @returns {boolean} - true if the status is successful, false otherwise */ -export function hasSucceed(networkSettingsStatus) { +export function hasSucceeded(networkSettingsStatus) { return networkSettingsStatus === NETWORKSETTINGS_STATUS.SUCCESSFUL; } diff --git a/src/screens/Settings.js b/src/screens/Settings.js index 3fd30fc31..150364eda 100644 --- a/src/screens/Settings.js +++ b/src/screens/Settings.js @@ -14,6 +14,7 @@ import { Text, View, } from 'react-native'; +import { isEmpty } from 'lodash'; import OfflineBar from '../components/OfflineBar'; import Logo from '../components/Logo'; import { HathorList, ListItem, ListMenu } from '../components/HathorList'; @@ -32,9 +33,15 @@ import { isPushNotificationAvailableForUser } from '../utils'; * server {str} URL of server this wallet is connected to */ const mapStateToProps = (state) => { - const server = state.useWalletService - ? state.wallet.storage.config.getWalletServiceBaseUrl() - : state.wallet.storage.config.getServerUrl(); + let server; + const { walletServiceUrl } = state.networkSettings; + if (state.useWalletService && !isEmpty(walletServiceUrl)) { + server = walletServiceUrl; + } + + if (!server) { + server = state.networkSettings.nodeUrl; + } return { selectedToken: state.selectedToken, diff --git a/src/screens/UnregisterToken.js b/src/screens/UnregisterToken.js index 18d91a2d5..c5384b5b5 100644 --- a/src/screens/UnregisterToken.js +++ b/src/screens/UnregisterToken.js @@ -76,7 +76,7 @@ class UnregisterToken extends React.Component { // XXX: maybe we should create a new action `removeToken` // so we dont need to get all registered tokens to call setTokens const promise = this.props.storage.unregisterToken(tokenUnregister).then(async () => { - const newTokens = []; + const newTokens = {}; const iterator = this.props.storage.getRegisteredTokens(); let next = await iterator.next(); @@ -85,11 +85,7 @@ class UnregisterToken extends React.Component { while (!next.done) { const token = next.value; // We need to filter the token data to remove the metadata from this list (e.g. balance) - newTokens.push({ - uid: token.uid, - symbol: token.symbol, - name: token.name, - }); + newTokens[token.uid] = { ...token }; // eslint-disable-next-line no-await-in-loop next = await iterator.next(); } diff --git a/src/utils.js b/src/utils.js index d595649c0..86436201c 100644 --- a/src/utils.js +++ b/src/utils.js @@ -11,8 +11,9 @@ import React from 'react'; import { t } from 'ttag'; import { Linking, Platform, Text } from 'react-native'; import { getStatusBarHeight } from 'react-native-status-bar-height'; +import { isEmpty } from 'lodash'; import baseStyle from './styles/init'; -import { KEYCHAIN_USER } from './constants'; +import { KEYCHAIN_USER, NETWORK_MAINNET } from './constants'; import { STORE } from './store'; import { TxHistory } from './models'; import { COLORS, STYLE } from './styles/themes'; @@ -383,5 +384,15 @@ export function combineURLs(baseURL, relativeURL) { * @returns {Boolean} true if available, false otherwise. */ export const isPushNotificationAvailableForUser = (state) => ( - state.pushNotification.available && state.pushNotification.deviceRegistered + state.pushNotification.available + // On iOS a simulator can't register a device token on APNS + && state.pushNotification.deviceRegistered + // TODO: We should drop this condition when we add support other networks + // XXX: We don't have support in this app to generate device tokens + // to the FCM testnet app. Currently we embbed only the mainnet + // configuration file during the build. + && state.networkSettings.network === NETWORK_MAINNET + // If Wallet Service URLs are empty it makes impossible to use the + // Wallet Service API to register the device's token. + && !isEmpty(state.networkSettings.walletServiceUrl) );