diff --git a/App.js b/App.js index 6799378f7..757362e00 100644 --- a/App.js +++ b/App.js @@ -1,7 +1,6 @@ import React from 'react'; import { Linking } from 'react-native'; import { NavigationActions } from 'react-navigation'; - import MainBottomTabs from './MainBottomTabs'; export default class App extends React.Component { diff --git a/App.test.js b/App.test.js index 24f2f56c4..6033d4bb3 100644 --- a/App.test.js +++ b/App.test.js @@ -6,6 +6,7 @@ import Settings from './screen/settings/settings'; import Selftest from './screen/selftest'; import { BlueHeader } from './BlueComponents'; import MockStorage from './MockStorage'; +import { FiatUnit } from './models/fiatUnit'; global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment let assert = require('assert'); jest.mock('react-native-custom-qr-codes', () => 'Video'); @@ -74,7 +75,7 @@ it('BlueHeader works', () => { expect(rendered).toBeTruthy(); }); -it.skip('Settings work', () => { +it('Settings work', () => { const rendered = TestRenderer.create().toJSON(); expect(rendered).toBeTruthy(); }); @@ -302,10 +303,24 @@ describe('currency', () => { AsyncStorage.storageCache = {}; // cleanup from other tests let currency = require('./currency'); await currency.startUpdater(); - let cur = AsyncStorage.storageCache[AppStorage.CURRENCY]; + let cur = AsyncStorage.storageCache[AppStorage.EXCHANGE_RATES]; cur = JSON.parse(cur); assert.ok(Number.isInteger(cur[currency.STRUCT.LAST_UPDATED])); assert.ok(cur[currency.STRUCT.LAST_UPDATED] > 0); - assert.ok(cur[currency.STRUCT.BTC_USD] > 0); + assert.ok(cur['BTC_USD'] > 0); + + // now, setting other currency as default + AsyncStorage.storageCache[AppStorage.PREFERRED_CURRENCY] = JSON.stringify(FiatUnit.JPY); + await currency.startUpdater(); + cur = JSON.parse(AsyncStorage.storageCache[AppStorage.EXCHANGE_RATES]); + assert.ok(cur['BTC_JPY'] > 0); + + // now setting with a proper setter + await currency.setPrefferedCurrency(FiatUnit.EUR); + await currency.startUpdater(); + let preferred = await currency.getPreferredCurrency(); + assert.equal(preferred.endPointKey, 'EUR'); + cur = JSON.parse(AsyncStorage.storageCache[AppStorage.EXCHANGE_RATES]); + assert.ok(cur['BTC_EUR'] > 0); }); }); diff --git a/BlueComponents.js b/BlueComponents.js index c0badee8e..3badbbebe 100644 --- a/BlueComponents.js +++ b/BlueComponents.js @@ -536,6 +536,12 @@ const stylesBlueIcon = StyleSheet.create({ backgroundColor: '#d2f8d6', transform: [{ rotate: '-45deg' }], }, + ballIncommingWithoutRotate: { + width: 30, + height: 30, + borderRadius: 15, + backgroundColor: '#d2f8d6', + }, ballReceive: { width: 30, height: 30, @@ -550,6 +556,12 @@ const stylesBlueIcon = StyleSheet.create({ backgroundColor: '#f8d2d2', transform: [{ rotate: '225deg' }], }, + ballOutgoingWithoutRotate: { + width: 30, + height: 30, + borderRadius: 15, + backgroundColor: '#f8d2d2', + }, ballTransparrent: { width: 30, height: 30, @@ -622,6 +634,20 @@ export class BlueTransactionPendingIcon extends Component { } } +export class BlueTransactionExpiredIcon extends Component { + render() { + return ( + + + + + + + + ); + } +} + export class BlueTransactionOnchainIcon extends Component { render() { return ( @@ -648,15 +674,8 @@ export class BlueTransactionOffchainIcon extends Component { return ( - - + + @@ -669,15 +688,8 @@ export class BlueTransactionOffchainIncomingIcon extends Component { return ( - - + + @@ -705,28 +717,26 @@ export class BlueReceiveButtonIcon extends Component { render() { return ( - - + + - + - {loc.receive.header.toLowerCase()} + {loc.receive.header} @@ -750,18 +760,15 @@ export class BlueSendButtonIcon extends Component { render() { return ( - - + + - + - {loc.send.header.toLowerCase()} + {loc.send.header} @@ -794,22 +802,21 @@ export class ManageFundsBigButton extends Component { render() { return ( - - + + @@ -1109,47 +1116,65 @@ export class BlueBitcoinAmount extends Component { amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), onChangeText: PropTypes.func, disabled: PropTypes.bool, + unit: PropTypes.string, + }; + + static defaultProps = { + unit: BitcoinUnit.BTC, }; render() { const amount = typeof this.props.amount === 'number' ? this.props.amount.toString() : this.props.amount; return ( - - - this.props.onChangeText(text.replace(',', '.'))} - placeholder="0" - maxLength={10} - editable={!this.props.isLoading && !this.props.disabled} - value={amount} - placeholderTextColor={this.props.disabled ? '#99a0ab' : '#0f5cc0'} - style={{ - color: this.props.disabled ? '#99a0ab' : '#0f5cc0', - fontSize: 36, - fontWeight: '600', - }} - /> - - {' ' + BitcoinUnit.BTC} - + this.textInput.focus()}> + + + + this.props.onChangeText( + this.props.unit === BitcoinUnit.BTC + ? text.replace(new RegExp('[^0-9.]'), '', '.') + : text.replace(new RegExp('[^0-9]'), ''), + ) + } + placeholder="0" + maxLength={10} + ref={textInput => (this.textInput = textInput)} + editable={!this.props.isLoading && !this.props.disabled} + value={amount} + placeholderTextColor={this.props.disabled ? '#99a0ab' : '#0f5cc0'} + style={{ + color: this.props.disabled ? '#99a0ab' : '#0f5cc0', + fontSize: 36, + fontWeight: '600', + }} + {...this.props} + /> + + {' ' + this.props.unit} + + + + + {loc.formatBalance( + this.props.unit === BitcoinUnit.BTC ? amount || 0 : loc.formatBalanceWithoutSuffix(amount || 0, BitcoinUnit.BTC), + BitcoinUnit.LOCAL_CURRENCY, + )} + + - - - {loc.formatBalance(amount || 0, BitcoinUnit.LOCAL_CURRENCY)} - - - + ); } } diff --git a/HDWallet.test.js b/HDWallet.test.js index 4c47d4335..880cbcf0c 100644 --- a/HDWallet.test.js +++ b/HDWallet.test.js @@ -10,6 +10,9 @@ it('can convert witness to address', () => { address = SegwitBech32Wallet.witnessToAddress('035c618df829af694cb99e664ce1b34f80ad2c3b49bcd0d9c0b1836c66b2d25fd8'); assert.equal(address, 'bc1quhnve8q4tk3unhmjts7ymxv8cd6w9xv8wy29uv'); + + address = SegwitBech32Wallet.scriptPubKeyToAddress('00144d757460da5fcaf84cc22f3847faaa1078e84f6a'); + assert.equal(address, 'bc1qf46hgcx6tl90snxz9uuy0742zpuwsnm27ysdh7'); }); it('can create a Segwit HD (BIP49)', async function() { diff --git a/LightningCustodianWallet.test.js b/LightningCustodianWallet.test.js index 397786b7a..0903baee5 100644 --- a/LightningCustodianWallet.test.js +++ b/LightningCustodianWallet.test.js @@ -6,12 +6,6 @@ let assert = require('assert'); describe('LightningCustodianWallet', () => { let l1 = new LightningCustodianWallet(); - it.skip('can issue wallet credentials', async () => { - let l0 = new LightningCustodianWallet(); - await l0.createAccount(); - console.log(l0.getSecret()); - }); - it('can create, auth and getbtc', async () => { jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; assert.ok(l1.refill_addressess.length === 0); @@ -207,7 +201,12 @@ describe('LightningCustodianWallet', () => { assert.ok(invoices2[0].description); assert.equal(invoices2[0].description, 'test memo'); assert.ok(invoices2[0].payment_request); + assert.ok(invoices2[0].timestamp); + assert.ok(invoices2[0].expire_time); assert.equal(invoices2[0].amt, 1); + for (let inv of invoices2) { + assert.equal(inv.type, 'user_invoice'); + } await lOld.fetchBalance(); let oldBalance = lOld.balance; @@ -248,4 +247,69 @@ describe('LightningCustodianWallet', () => { assert.equal(lOld.balance - oldBalance, 1); assert.equal(lNew.balance, 0); }); + + it('can pay free amount (tip) invoice', async function() { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 100 * 1000; + if (!process.env.BLITZHUB) { + console.error('process.env.BLITZHUB not set, skipped'); + return; + } + + // fetchig invoice from tippin.me : + + const api = new Frisbee({ + baseURI: 'https://tippin.me', + }); + const res = await api.post('/lndreq/newinvoice.php', { + headers: { + Origin: 'https://tippin.me', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'en-GB,en-US;q=0.9,en;q=0.8', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Accept: 'application/json, text/javascript, */*; q=0.01', + }, + body: 'userid=1188&username=overtorment&istaco=0&customAmnt=0&customMemo=', + }); + + let json; + let invoice; + if (res && res.body && (json = JSON.parse(res.body)) && json.message) { + invoice = json.message; + } else { + throw new Error('tippin.me problem: ' + JSON.stringify(res)); + } + + // --> use to pay specific invoice + // invoice = + // 'lnbc1pwrp35spp5z62nvj8yw6luq7ns4a8utpwn2qkkdwdt0ludwm54wjeazk2xv5wsdpu235hqurfdcsx7an9wf6x7undv4h8ggpgw35hqurfdchx6eff9p6nzvfc8q5scqzysxqyz5vqj8xq6wz6dezmunw6qxleuw67ensjnt3fldltrmmkvzurge0dczpn94fkwwh7hkh5wqrhsvfegtvhswn252hn6uw5kx99dyumz4v5n9sp337py2'; + + let l2 = new LightningCustodianWallet(); + l2.setSecret(process.env.BLITZHUB); + await l2.authorize(); + await l2.fetchTransactions(); + await l2.fetchBalance(); + let oldBalance = +l2.balance; + let txLen = l2.transactions_raw.length; + + let decoded = await l2.decodeInvoice(invoice); + assert.ok(decoded.payment_hash); + assert.ok(decoded.description); + assert.equal(+decoded.num_satoshis, 0); + + await l2.checkRouteInvoice(invoice); + + let start = +new Date(); + await l2.payInvoice(invoice, 3); + let end = +new Date(); + if ((end - start) / 1000 > 9) { + console.warn('payInvoice took', (end - start) / 1000, 'sec'); + } + + await l2.fetchTransactions(); + assert.equal(l2.transactions_raw.length, txLen + 1); + // transactions became more after paying an invoice + + await l2.fetchBalance(); + assert.equal(oldBalance - l2.balance, 3); + }); }); diff --git a/MainBottomTabs.js b/MainBottomTabs.js index bac9b510c..0d9bfca22 100644 --- a/MainBottomTabs.js +++ b/MainBottomTabs.js @@ -35,6 +35,9 @@ import Success from './screen/send/success'; import ManageFunds from './screen/lnd/manageFunds'; import ScanLndInvoice from './screen/lnd/scanLndInvoice'; +import LNDCreateInvoice from './screen/lnd/lndCreateInvoice'; +import LNDViewInvoice from './screen/lnd/lndViewInvoice'; +import LNDViewAdditionalInvoiceInformation from './screen/lnd/lndViewAdditionalInvoiceInformation'; const ReorderWalletsStackNavigator = createStackNavigator({ ReorderWallets: { @@ -130,6 +133,55 @@ const CreateTransactionStackNavigator = createStackNavigator({ }, }); +const ManageFundsStackNavigator = createStackNavigator({ + ManageFunds: { + screen: ManageFunds, + }, + SelectWallet: { + screen: SelectWallet, + }, + SendDetails: { + screen: CreateTransactionStackNavigator, + navigationOptions: { + header: null, + }, + }, +}); + +const LNDViewInvoiceStackNavigator = createStackNavigator({ + LNDViewInvoice: { + screen: LNDViewInvoice, + swipeEnabled: false, + gesturesEnabled: false, + }, + LNDViewAdditionalInvoiceInformation: { + screen: LNDViewAdditionalInvoiceInformation, + }, +}); + +const LNDCreateInvoiceStackNavigator = createStackNavigator({ + LNDCreateInvoice: { + screen: LNDCreateInvoice, + }, + LNDViewInvoice: { + screen: LNDViewInvoice, + swipeEnabled: false, + gesturesEnabled: false, + }, + LNDViewAdditionalInvoiceInformation: { + screen: LNDViewAdditionalInvoiceInformation, + }, +}); + +const CreateWalletStackNavigator = createStackNavigator({ + AddWallet: { + screen: AddWallet, + }, + ImportWallet: { + screen: ImportWallet, + }, +}); + const MainBottomTabs = createStackNavigator( { Wallets: { @@ -140,10 +192,10 @@ const MainBottomTabs = createStackNavigator( }, }, AddWallet: { - screen: AddWallet, - }, - ImportWallet: { - screen: ImportWallet, + screen: CreateWalletStackNavigator, + navigationOptions: { + header: null, + }, }, ScanQrWif: { screen: scanQrWif, @@ -180,7 +232,10 @@ const MainBottomTabs = createStackNavigator( // LND: ManageFunds: { - screen: ManageFunds, + screen: ManageFundsStackNavigator, + navigationOptions: { + header: null, + }, }, ScanLndInvoice: { screen: ScanLndInvoice, @@ -194,11 +249,17 @@ const MainBottomTabs = createStackNavigator( header: null, }, }, - - // Select Wallet. Mostly for deeplinking - - SelectWallet: { - screen: SelectWallet, + LNDCreateInvoice: { + screen: LNDCreateInvoiceStackNavigator, + navigationOptions: { + header: null, + }, + }, + LNDViewExistingInvoice: { + screen: LNDViewInvoiceStackNavigator, + navigationOptions: { + header: null, + }, }, }, { diff --git a/android/app/build.gradle b/android/app/build.gradle index ee72e3411..1ea5c0850 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -102,8 +102,8 @@ android { applicationId "io.bluewallet.bluewallet" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 12 - versionName "3.4.0" + versionCode 15 + versionName "3.5.5" ndk { abiFilters "armeabi-v7a", "x86" } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1c224c19f..bb0369755 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ + diff --git a/android/build/intermediates/lint-cache/maven.google/master-index.xml b/android/build/intermediates/lint-cache/maven.google/master-index.xml index 9abc71172..67cca333a 100644 --- a/android/build/intermediates/lint-cache/maven.google/master-index.xml +++ b/android/build/intermediates/lint-cache/maven.google/master-index.xml @@ -112,4 +112,9 @@ + + + + + diff --git a/android/metadata/en-US/changelogs/14.txt b/android/metadata/en-US/changelogs/14.txt new file mode 100644 index 000000000..0c6965929 --- /dev/null +++ b/android/metadata/en-US/changelogs/14.txt @@ -0,0 +1,10 @@ +v3.5.0 +------ + +ADD: Create LND invoice +ADD: Ability to show wallet XPUB in options +ADD: translations for german (DE) +ADD: Set receive amount & label +ADD: Added more Fiat currencies +ADD: help text in lighning settings +ADD: CZ locale diff --git a/android/metadata/en-US/changelogs/15.txt b/android/metadata/en-US/changelogs/15.txt new file mode 100644 index 000000000..f21432ff7 --- /dev/null +++ b/android/metadata/en-US/changelogs/15.txt @@ -0,0 +1,9 @@ +v3.5.5 +------ + +ADD: pay zero-amount (tip) invoices +ADD: lightning withdrawal through zigzag +ADD: Thai translation +ADD: Dutch translation +ADD: Added Singapore Dollars +ADD: Added AUD, VEF, and ZAR fiats. diff --git a/class/app-storage.js b/class/app-storage.js index fc01d213b..ad9d9c62f 100644 --- a/class/app-storage.js +++ b/class/app-storage.js @@ -14,9 +14,9 @@ let encryption = require('../encryption'); export class AppStorage { static FLAG_ENCRYPTED = 'data_encrypted'; static LANG = 'lang'; - static CURRENCY = 'currency'; + static EXCHANGE_RATES = 'currency'; static LNDHUB = 'lndhub'; - static PREFERREDCURRENCY = 'preferredCurrency'; + static PREFERRED_CURRENCY = 'preferredCurrency'; constructor() { /** {Array.} */ @@ -174,8 +174,10 @@ export class AppStorage { break; } // done - this.wallets.push(unserializedWallet); - this.tx_metadata = data.tx_metadata; + if (!this.wallets.some(wallet => wallet.getSecret() === unserializedWallet.secret)) { + this.wallets.push(unserializedWallet); + this.tx_metadata = data.tx_metadata; + } } return true; } else { @@ -290,11 +292,23 @@ export class AppStorage { for (let wallet of this.wallets) { if (c++ === index) { await wallet.fetchTransactions(); + if (wallet.fetchPendingTransactions) { + await wallet.fetchPendingTransactions(); + } + if (wallet.fetchUserInvoices) { + await wallet.fetchUserInvoices(); + } } } } else { for (let wallet of this.wallets) { await wallet.fetchTransactions(); + if (wallet.fetchPendingTransactions) { + await wallet.fetchPendingTransactions(); + } + if (wallet.fetchUserInvoices) { + await wallet.fetchUserInvoices(); + } } } } diff --git a/class/lightning-custodian-wallet.js b/class/lightning-custodian-wallet.js index d93ad8999..9c0cacf76 100644 --- a/class/lightning-custodian-wallet.js +++ b/class/lightning-custodian-wallet.js @@ -102,9 +102,9 @@ export class LightningCustodianWallet extends LegacyWallet { this.secret = 'lndhub://' + json.login + ':' + json.password; } - async payInvoice(invoice) { + async payInvoice(invoice, freeAmount = 0) { let response = await this._api.post('/payinvoice', { - body: { invoice: invoice }, + body: { invoice: invoice, amount: freeAmount }, headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json', @@ -145,12 +145,24 @@ export class LightningCustodianWallet extends LegacyWallet { throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); } + this.user_invoices_raw = json; + return json; } + /** + * Basically the same as this.getUserInvoices() but saves invoices list + * to internal variable + * + * @returns {Promise} + */ + async fetchUserInvoices() { + await this.getUserInvoices(); + } + async addInvoice(amt, memo) { let response = await this._api.post('/addinvoice', { - body: { amt: amt + '', memo: encodeURIComponent(memo) }, + body: { amt: amt + '', memo: memo }, headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json', @@ -306,8 +318,9 @@ export class LightningCustodianWallet extends LegacyWallet { getTransactions() { let txs = []; this.pending_transactions_raw = this.pending_transactions_raw || []; + this.user_invoices_raw = this.user_invoices_raw || []; this.transactions_raw = this.transactions_raw || []; - txs = txs.concat(this.pending_transactions_raw, this.transactions_raw.slice().reverse()); // slice so array is cloned + txs = txs.concat(this.pending_transactions_raw.slice(), this.transactions_raw.slice().reverse(), this.user_invoices_raw.slice()); // slice so array is cloned // transforming to how wallets/list screen expects it for (let tx of txs) { if (tx.amount) { @@ -325,13 +338,21 @@ export class LightningCustodianWallet extends LegacyWallet { if (tx.type === 'paid_invoice') { tx.memo = tx.memo || 'Lightning payment'; + if (tx.value > 0) tx.value = (tx.value * 1 + tx.fee * 1) * -1; + // outer code expects spending transactions to of negative value } if (tx.type === 'bitcoind_tx') { tx.memo = 'On-chain transaction'; } - tx.received = new Date(tx.timestamp * 1000).toString(); // TODO once api is ready + if (tx.type === 'user_invoice') { + // incoming ln tx + tx.value = parseInt(tx.amt); + tx.memo = tx.description || 'Lightning invoice'; + } + + tx.received = new Date(tx.timestamp * 1000).toString(); } return txs.sort(function(a, b) { return b.timestamp - a.timestamp; @@ -497,7 +518,7 @@ export class LightningCustodianWallet extends LegacyWallet { } allowReceive() { - return false; + return true; } } diff --git a/class/segwit-bech-wallet.js b/class/segwit-bech-wallet.js index a7a2904e2..e8293d20f 100644 --- a/class/segwit-bech-wallet.js +++ b/class/segwit-bech-wallet.js @@ -27,4 +27,10 @@ export class SegwitBech32Wallet extends LegacyWallet { const scriptPubKey = bitcoin.script.witnessPubKeyHash.output.encode(pubKeyHash); return bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.bitcoin); } + + static scriptPubKeyToAddress(scriptPubKey) { + const bitcoin = require('bitcoinjs-lib'); + const scriptPubKey2 = Buffer.from(scriptPubKey, 'hex'); + return bitcoin.address.fromOutputScript(scriptPubKey2, bitcoin.networks.bitcoin); + } } diff --git a/currency.js b/currency.js index f8afcd9ce..60bc1981f 100644 --- a/currency.js +++ b/currency.js @@ -4,37 +4,46 @@ import { AppStorage } from './class'; import { FiatUnit } from './models/fiatUnit'; let BigNumber = require('bignumber.js'); let preferredFiatCurrency = FiatUnit.USD; -let lang = {}; -// let btcusd = 6500; // default +let exchangeRates = {}; const STRUCT = { LAST_UPDATED: 'LAST_UPDATED', - BTC_USD: 'BTC_USD', - BTC_EUR: 'BTC_EUR', }; +/** + * Saves to storage preferred currency, whole object + * from `./models/fiatUnit` + * + * @param item {Object} one of the values in `./models/fiatUnit` + * @returns {Promise} + */ +async function setPrefferedCurrency(item) { + await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(item)); +} + +async function getPreferredCurrency() { + return JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY)); +} + async function updateExchangeRate() { - let preferredFiatCurrency; - try { - preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERREDCURRENCY)); - if (preferredFiatCurrency === null) { - throw Error(); - } - } catch (_error) { - preferredFiatCurrency = FiatUnit.USD; - } - if (+new Date() - lang[STRUCT.LAST_UPDATED] <= 30 * 60 * 1000) { + if (+new Date() - exchangeRates[STRUCT.LAST_UPDATED] <= 30 * 60 * 1000) { // not updating too often return; } + + try { + preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY)); + } catch (_) {} + preferredFiatCurrency = preferredFiatCurrency || FiatUnit.USD; + let json; try { const api = new Frisbee({ - baseURI: 'https://www.bitstamp.net', + baseURI: 'https://api.coindesk.com', }); - let response = await api.get('/api/v2/ticker/' + preferredFiatCurrency.endPointKey); - json = response.body; - if (typeof json === 'undefined' || typeof json.last === 'undefined') { + let response = await api.get('/v1/bpi/currentprice/' + preferredFiatCurrency.endPointKey + '.json'); + json = JSON.parse(response.body); + if (!json || !json.bpi || !json.bpi[preferredFiatCurrency.endPointKey] || !json.bpi[preferredFiatCurrency.endPointKey].rate_float) { throw new Error('Could not update currency rate: ' + response.err); } } catch (Err) { @@ -42,53 +51,51 @@ async function updateExchangeRate() { return; } - lang[STRUCT.LAST_UPDATED] = +new Date(); - lang[STRUCT[preferredFiatCurrency.storageKey]] = json.last * 1; - await AsyncStorage.setItem(AppStorage.CURRENCY, JSON.stringify(lang)); + exchangeRates[STRUCT.LAST_UPDATED] = +new Date(); + exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = json.bpi[preferredFiatCurrency.endPointKey].rate_float * 1; + await AsyncStorage.setItem(AppStorage.EXCHANGE_RATES, JSON.stringify(exchangeRates)); + await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(preferredFiatCurrency)); } -async function startUpdater(force = false) { - if (force) { - const lang = JSON.parse(await AsyncStorage.getItem(AppStorage.CURRENCY)); - delete lang[STRUCT.LAST_UPDATED]; - await AsyncStorage.setItem(AppStorage.CURRENCY, JSON.stringify(lang)); - try { - preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERREDCURRENCY)); - if (preferredFiatCurrency === null) { - throw Error(); - } - } catch (_error) { - preferredFiatCurrency = FiatUnit.USD; - } +let interval = false; +async function startUpdater() { + if (interval) { + clearInterval(interval); + exchangeRates[STRUCT.LAST_UPDATED] = 0; } - lang = await AsyncStorage.getItem(AppStorage.CURRENCY); - try { - lang = JSON.parse(lang); - } catch (Err) { - lang = {}; - } - lang = lang || {}; - lang[STRUCT.LAST_UPDATED] = lang[STRUCT.LAST_UPDATED] || 0; - lang[STRUCT[preferredFiatCurrency.storageKey]] = lang[STRUCT[preferredFiatCurrency.storageKey]] || 6500; - setInterval(() => updateExchangeRate(), 2 * 60 * 100); + + interval = setInterval(() => updateExchangeRate(), 2 * 60 * 100); return updateExchangeRate(); } function satoshiToLocalCurrency(satoshi) { - if (!lang[STRUCT[preferredFiatCurrency.storageKey]]) return satoshi; + if (!exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) return satoshi; let b = new BigNumber(satoshi); b = b .dividedBy(100000000) - .multipliedBy(lang[STRUCT[preferredFiatCurrency.storageKey]]) + .multipliedBy(exchangeRates['BTC_' + preferredFiatCurrency.endPointKey]) .toString(10); b = parseFloat(b).toFixed(2); - const formatter = new Intl.NumberFormat('en-US', { - style: 'currency', - currency: preferredFiatCurrency.formatterValue, - minimumFractionDigits: 2, - }); + let formatter; + + try { + formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, { + style: 'currency', + currency: preferredFiatCurrency.endPointKey, + minimumFractionDigits: 2, + }); + } catch (error) { + console.warn(error); + console.log(error); + formatter = new Intl.NumberFormat(FiatUnit.USD.locale, { + style: 'currency', + currency: preferredFiatCurrency.endPointKey, + minimumFractionDigits: 2, + }); + } + return formatter.format(b); } @@ -111,3 +118,5 @@ module.exports.STRUCT = STRUCT; module.exports.satoshiToLocalCurrency = satoshiToLocalCurrency; module.exports.satoshiToBTC = satoshiToBTC; module.exports.BTCToLocalCurrency = BTCToLocalCurrency; +module.exports.setPrefferedCurrency = setPrefferedCurrency; +module.exports.getPreferredCurrency = getPreferredCurrency; diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme index 57a8c97a9..c71cef30f 100644 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme +++ b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/BlueWallet.xcscheme @@ -80,7 +80,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.4.0 + 3.5.6 CFBundleSignature ???? CFBundleURLTypes @@ -33,7 +33,7 @@ CFBundleVersion - 168 + 216 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS @@ -56,7 +56,7 @@ NSCalendarsUsageDescription This alert should not show up as we do not require this data NSCameraUsageDescription - In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code. + In order to quickly scan the recipient's address, we need your permission to use the camera to scan their QR Code. NSLocationWhenInUseUsageDescription This alert should not show up as we do not require this data NSMotionUsageDescription diff --git a/ios/fastlane/metadata/copyright.txt b/ios/fastlane/metadata/copyright.txt index ea910cc84..ddacfeeff 100644 --- a/ios/fastlane/metadata/copyright.txt +++ b/ios/fastlane/metadata/copyright.txt @@ -1 +1 @@ -2018 BlueWallet Services S.R.L. +2019 BlueWallet Services S.R.L. diff --git a/ios/fastlane/metadata/en-US/release_notes.txt b/ios/fastlane/metadata/en-US/release_notes.txt index b4ba2216e..e56eec844 100644 --- a/ios/fastlane/metadata/en-US/release_notes.txt +++ b/ios/fastlane/metadata/en-US/release_notes.txt @@ -1,11 +1,25 @@ -ADD: Persist the preferred unit per wallet. -ADD: Deeplinking for bitcoin, lightning -ADD: Added fee in tx details -ADD: Fiat Currency in settings panel -ADD: Select Wallet on Send screen -ADD: Currency settings panel (eur, usd) -FIX: Disabled autocorrect when importing wallet (security issue) -FIX: amount display bug -ADD: spend bip44 (legacy HD wallets) -FIX: BIP44 mnemonic QR correctly imported -ADD: haptic feedback when the user only has 1 wallet \ No newline at end of file +v3.5.5 +------ + +ADD: pay zero-amount (tip) invoices +ADD: lightning withdrawal through zigzag +ADD: Thai translation +ADD: Dutch translation +ADD: Added Singapore Dollars +ADD: Added AUD, VEF, and ZAR fiats. +FIX: Loading indicator when creating a wallet +FIX: Changelly link +Fix and improve pt-BR translation +FIX: Cannot click on Lightning transactions #196 +FIX: Fixed a clipping issue in lightning settings +FIX: fixed a margin issue in about that caused clipping +FIX: Changed invoice description field to label +FIX: Updated transaction buttons maximum width +FIX: Main Buttons layout #204 +FIX: Add topup indication on wallet selection #207 +FIX: Invoice QR code wrong scale #203 +FIX: Don't allow user to pay for an invoice created with the same wallet. +FIX: If. balance was not a string, app would crash. +FIX: Changed language selection screen to FlatList +FIX: Made amount tap area larger +FIX: Fixed an issue in currency settings where the checkmark wouldn't be in the correct preference \ No newline at end of file diff --git a/ios/fastlane/metadata/pt-PT/description.txt b/ios/fastlane/metadata/pt-PT/description.txt new file mode 100644 index 000000000..e2fdfddfe --- /dev/null +++ b/ios/fastlane/metadata/pt-PT/description.txt @@ -0,0 +1,37 @@ +Guardar, enviar e receber bitcoin com uma carteira focada na segurança e simplicidade. + +Na Blue Wallet você possui as suas chaves privadas. Uma carteira Bitcoin focada nos usuários. + +Você pode instantaneamente transacionar com qualquer pessoa no mundo e transformar o sistema financeiro diretamente do seu bolso. + +Crie carteiras Bitcoin ilimitadas e gratuitamente, ou acesse as suas carteiras existentes através do seu dispositivo iOS. É simples e rápido. + +As funcionalidades disponíveis: + +1 - Segurança por design + +Open Source +Este aplicativo tem licença MIT, pode construir e lança-lo você mesmo! Feito em ReactNative + +Negação plausível +Senha falsa que decripta wallets falsas. Para casos especias onde possa ser obrigado a revelar a sua senha, pode revelar a senha falsa, mantenha as suas bitcoin protegidas + +Encriptação completa +Construída em cima da multi-camada de encritação do iOS, A BlueWallet encripta tudo com uma adicional senha de usuário + +Carteiras SegWit e HD +SegWit suportado (Ajuda a diminuir os fees ainda mais) e carteiras HD activadas + +2 - Focada na sua experiência + +Esteja em control +As chaves privadas nunca saiem do celular, você controla as suas chaves privadas + +Fees flexíveis +A começar em 1 Satoshi. Não page a mais por transações + +Substituição de Fee (RBF) +Acelere as suas transações aumentendo o fee (BIP125). Pode também alterar o endereço de destinatário em transações não confirmadas + +Carteira Sentinela +Carteira de "assitir apenas", observe o seu armazenamento externo de Bitcoin sem ter de lhe tocar diff --git a/ios/fastlane/metadata/pt-PT/keywords.txt b/ios/fastlane/metadata/pt-PT/keywords.txt new file mode 100644 index 000000000..365972b20 --- /dev/null +++ b/ios/fastlane/metadata/pt-PT/keywords.txt @@ -0,0 +1 @@ +bitcoin,wallet,segwit,crypto,blockchain,btc,cryptocurrency,wallet,bread,samourai,lightning,ethereum \ No newline at end of file diff --git a/ios/fastlane/metadata/pt-PT/marketing_url.txt b/ios/fastlane/metadata/pt-PT/marketing_url.txt new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/fastlane/metadata/pt-PT/marketing_url.txt @@ -0,0 +1 @@ + diff --git a/ios/fastlane/metadata/pt-PT/name.txt b/ios/fastlane/metadata/pt-PT/name.txt new file mode 100644 index 000000000..1cdc33df3 --- /dev/null +++ b/ios/fastlane/metadata/pt-PT/name.txt @@ -0,0 +1 @@ +BlueWallet - Bitcoin wallet diff --git a/ios/fastlane/metadata/pt-PT/privacy_url.txt b/ios/fastlane/metadata/pt-PT/privacy_url.txt new file mode 100644 index 000000000..849468985 --- /dev/null +++ b/ios/fastlane/metadata/pt-PT/privacy_url.txt @@ -0,0 +1 @@ +http://www.bluewallet.io/privacy.txt diff --git a/ios/fastlane/metadata/pt-PT/promotional_text.txt b/ios/fastlane/metadata/pt-PT/promotional_text.txt new file mode 100644 index 000000000..2f1abfbd9 --- /dev/null +++ b/ios/fastlane/metadata/pt-PT/promotional_text.txt @@ -0,0 +1,10 @@ +Features + +* Open Source +* Full encryption +* Plausible deniability +* Flexible fees +* Replace-By-Fee (RBF) +* SegWit +* Watch-only (Sentinel) wallets +* Lightning network \ No newline at end of file diff --git a/ios/fastlane/metadata/pt-PT/release_notes.txt b/ios/fastlane/metadata/pt-PT/release_notes.txt new file mode 120000 index 000000000..457102d1e --- /dev/null +++ b/ios/fastlane/metadata/pt-PT/release_notes.txt @@ -0,0 +1 @@ +../en-US/release_notes.txt \ No newline at end of file diff --git a/ios/fastlane/metadata/pt-PT/subtitle.txt b/ios/fastlane/metadata/pt-PT/subtitle.txt new file mode 100644 index 000000000..37871267a --- /dev/null +++ b/ios/fastlane/metadata/pt-PT/subtitle.txt @@ -0,0 +1 @@ +Bitcoin & Lightning \ No newline at end of file diff --git a/ios/fastlane/metadata/pt-PT/support_url.txt b/ios/fastlane/metadata/pt-PT/support_url.txt new file mode 100644 index 000000000..0369559b2 --- /dev/null +++ b/ios/fastlane/metadata/pt-PT/support_url.txt @@ -0,0 +1 @@ +https://github.com/BlueWallet/BlueWallet/issues diff --git a/loc/cs_CZ.js b/loc/cs_CZ.js new file mode 100644 index 000000000..1f302a118 --- /dev/null +++ b/loc/cs_CZ.js @@ -0,0 +1,213 @@ +module.exports = { + _: { + storage_is_encrypted: 'Vaše úložiště je zašifrované. Zadejte heslo k odemčení', + enter_password: 'Zadejte heslo', + bad_password: 'Špatné heslo, prosím zkuste to znovu', + months_ago: 'měsíců', + days_ago: 'dní', + hours_ago: 'hodin', + minutes_ago: 'minut', + never: 'nikdy', + }, + wallets: { + select_wallet: 'Vyberte peněženku', + options: 'možnosti', + list: { + app_name: 'Blue Wallet', + title: 'peněženky', + header: 'Peněženka reprezentuje pár tajného (privátního) klíče a adresy' + 'kterou můžete sdílet, abyste získali mince', + add: 'Přidat peněženku', + create_a_wallet: 'Vytvořit peněženku', + create_a_wallet1: 'Je to zdarma a můžete vytvořit', + create_a_wallet2: 'tolik, kolik budete chtít', + latest_transaction: 'poslední transakce', + empty_txs1: 'Zde budou zobrazeny vaše transakce,', + empty_txs2: 'zatím žádné', + tap_here_to_buy: 'Klikněte zde pro zakoupení Bitcoinu', + }, + reorder: { + title: 'Seřadit peěženky', + }, + add: { + title: 'přidat peněženku', + description: + 'Můžete naskenovat zálohovoanou papírovou peněženku (WIF - Wallet Import Format), nevo vytvořit novou peněženku. Segwit peněženky jsou podporovány standardně.', + scan: 'Skenovat', + create: 'Vytvořit', + label_new_segwit: 'Nová SegWit', + label_new_lightning: 'Nová Lightning', + wallet_name: 'název peněženky', + wallet_type: 'typ', + or: 'nebo', + import_wallet: 'Importovat peněženku', + imported: 'Importována', + coming_soon: 'Již brzy', + lightning: 'Lightning', + bitcoin: 'Bitcoin', + }, + details: { + title: 'Peněženka', + address: 'Adresa', + type: 'Typ', + label: 'Popisek', + destination: 'cíl', + description: 'Popis', + are_you_sure: 'Jste si jistý?', + yes_delete: 'Ano, smazat', + no_cancel: 'Ne, zrušit', + delete: 'Smazat', + save: 'Uložit', + delete_this_wallet: 'Smazat peněženku', + export_backup: 'Exportovat / zálohovat', + buy_bitcoin: 'Koupit Bitcoin', + show_xpub: 'Ukázat XPUB', + }, + export: { + title: 'exportovat peněženku', + }, + xpub: { + title: 'XPUB peněženky', + copiedToClipboard: 'Zkopírováno do schránky.', + }, + import: { + title: 'importovat', + explanation: + 'Zadejte zde svůj mnemonic seed, privátní klíč, WIF, nebo cokoliv co máte. BlueWallet se pokusí uhodnout správný formát a naimportovat vaší peněženku', + imported: 'Importováno', + error: 'Chyba při importu. Prosím ujistěte se, že poskytnutá data jsou správná.', + success: 'Úspěch', + do_import: 'Importovat', + scan_qr: 'nebo raději naskenovat QR kód?', + }, + scanQrWif: { + go_back: 'Zpět', + cancel: 'Zrušit', + decoding: 'Dekóduji', + input_password: 'Vložte heslo', + password_explain: 'Toto je BIP38 zašifrovaný privátní klíč', + bad_password: 'Špatné heslo', + wallet_already_exists: 'Tato peněženka již existuje', + bad_wif: 'Špatný WIF', + imported_wif: 'Importovaný WIF ', + with_address: ' s adresou ', + imported_segwit: 'Importovaná SegWit', + imported_legacy: 'Importovaná Legacy', + imported_watchonly: 'Importovaná Watch-only', + }, + }, + transactions: { + list: { + tabBarLabel: 'Transakce', + title: 'transakce', + description: 'Seznam příchozích a odchozích transakcí vašich peněženek', + conf: 'potvrzení', + }, + details: { + title: 'Transakce', + from: 'Input', + to: 'Output', + copy: 'Kopírovat', + transaction_details: 'Detaily transakce', + show_in_block_explorer: 'Ukázat v block exploreru', + }, + }, + send: { + header: 'Poslat', + details: { + title: 'vytvořit transakci', + amount_field_is_not_valid: 'Čáskta není správně vyplněna', + fee_field_is_not_valid: 'Poplatek není správně vyplněn', + address_field_is_not_valid: 'Adresa není správně vyplněna', + total_exceeds_balance: 'Částka, kterou se snažíte poslat, přesahuje dostupný zůstatek.', + create_tx_error: 'Nastala chyba při vytváření transakce. Prosím ujistěte se, že adresa je platná.', + address: 'adresa', + amount_placeholder: 'částka k odeslání (v BTC)', + fee_placeholder: 'plus transakční poplatek (v BTC)', + note_placeholder: 'poznámka pro sebe', + cancel: 'Zrušit', + scan: 'Skenovat', + send: 'Poslat', + create: 'Vytvořit', + remaining_balance: 'Zbývající zůstatek', + }, + confirm: { + header: 'Potvrdit', + sendNow: 'Poslat hned', + }, + success: { + done: 'Hotovo', + }, + create: { + details: 'Detaily', + title: 'vytvořit transakci', + error: 'Chyba při vytváření transakce. Nesprávná adresa nebo částka?', + go_back: 'Zpět', + this_is_hex: 'Toto je vaše transakce, podepsána a připravena k odeslání do sítě.', + to: 'To', + amount: 'Částka', + fee: 'Poplatek', + tx_size: 'velikost transakce', + satoshi_per_byte: 'Satoshi/byte', + memo: 'Popisek', + broadcast: 'Odeslat do sítě', + not_enough_fee: 'Nedostatečný poplatek. Zvyšte poplatek.', + }, + }, + receive: { + header: 'Přijmout', + details: { + title: 'Sdílejte tuto adresu s plátcem', + share: 'sdílet', + copiedToClipboard: 'Zkopírováno do schránky.', + label: 'Popis', + setAmount: 'Přijmout částku...', + }, + }, + buyBitcoin: { + header: 'Koupit Bitcoin', + tap_your_address: 'Klikněte na svojí adresu pro zkopírování do schránky:', + copied: 'Zkopírováno do schránky.', + }, + settings: { + header: 'nastavení', + plausible_deniability: 'Plausible deniability...', + storage_not_encrypted: 'Uložiště: nezašifrováno', + storage_encrypted: 'Úložiště: zašifrováno', + password: 'Heslo', + password_explain: 'Vytořte si heslo k zašifrování úložiště.', + retype_password: 'Heslo znovu', + passwords_do_not_match: 'Hesla se neshodují', + encrypt_storage: 'Zašifrovat úložiště', + about: 'O BlueWallet', + language: 'Jazyk', + currency: 'Měna', + }, + plausibledeniability: { + title: 'Plausible Deniability', + help: + 'Za určitých okolností můžete být donuceni k prozrazení vašeho hesla.' + + 'K zajištění bezpečností vašich prostředků, BlueWallet může vytvořit' + + 'další zašifrované úložiště s rozdílný heslem. V případě potřeby' + + 'můžete toto heslo dát třetí straně. Pokud bude zadáno do BlueWallet,' + + 'odemkne nové "falešné" úložiště. Toto bude vypadat legitimně, ale' + + 'udrží vaše pravé hlavní úložiště v bezpečí.', + help2: 'Nové úložiště bude plně funkční, můžete na něj uložit minimální částku, aby vypadalo více uvěřitelně.', + create_fake_storage: 'Vytvořit falešné zašifrované úložiště', + go_back: 'Zpět', + create_password: 'Vytvořit heslo', + create_password_explanation: 'Heslo k falešnému úložišti nesmí být stejné jako heslo k hlavnímu úložišti', + password_should_not_match: 'Heslo k falešnému úložišti nesmí být stejné jako heslo k hlavnímu úložišti', + retype_password: 'Heslo znovu', + passwords_do_not_match: 'Hesla se neshodují, zkuste to znovu', + success: 'Úspěch', + }, + lnd: { + title: 'spravovat zůstatek', + choose_source_wallet: 'Vyberte zdrojovou peněženku', + refill_lnd_balance: 'Doplnit zůstatek na Lightning peněžence', + refill: 'Doplnit', + withdraw: 'Vybrat', + expired: 'Expirováno', + sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', + }, +}; diff --git a/loc/de_DE.js b/loc/de_DE.js index ebbdb5b10..aea5def9a 100644 --- a/loc/de_DE.js +++ b/loc/de_DE.js @@ -15,10 +15,11 @@ module.exports = { list: { app_name: 'Blue Wallet', title: 'Wallets', - header: 'Eine Wallet (Brieftasche) spiegelt ein Paar von kryptographischen Schlüssel wider. Einen geheimen und eine Adresse als öffentlichen Schlüssel. Letztern kann man zum Erhalt von Bitcoin teilen.', + header: + 'Eine Wallet (Brieftasche) spiegelt ein Paar von kryptographischen Schlüssel wider. Einen geheimen und eine Adresse als öffentlichen Schlüssel. Letztern kann man zum Erhalt von Bitcoin teilen.', add: 'Wallet hinzufügen', create_a_wallet: 'Wallet erstellen', - create_a_wallet1: "Es ist kostenlos und du kannst", + create_a_wallet1: 'Es ist kostenlos und du kannst', create_a_wallet2: 'so viele erstellen, wie du möchtest', latest_transaction: 'Lezte Transaktion', empty_txs1: 'Deine Transaktionen erscheinen hier', @@ -72,7 +73,7 @@ module.exports = { import: { title: 'Importieren', explanation: - "Gib hier deine mnemonische Phrase, deinen privaten Schlüssel, WIF oder worüber du auch immer verfügst ein. BlueWallet wird bestmöglich dein Format interpretieren und die Wallet importieren", + 'Gib hier deine mnemonische Phrase, deinen privaten Schlüssel, WIF oder worüber du auch immer verfügst ein. BlueWallet wird bestmöglich dein Format interpretieren und die Wallet importieren', imported: 'Importiert', error: 'Fehler beim Import. Ist die Eingabe korrekt?', success: 'Erfolg', @@ -209,5 +210,6 @@ module.exports = { refill_lnd_balance: 'Fülle deine Lightning Wallet auf', refill: 'Auffüllen', withdraw: 'Abheben', + sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', }, }; diff --git a/loc/en.js b/loc/en.js index 38a69106e..b8dcf696a 100644 --- a/loc/en.js +++ b/loc/en.js @@ -74,7 +74,7 @@ module.exports = { explanation: "Write here your mnemonic, private key, WIF, or anything you've got. BlueWallet will do its best to guess the correct format and import your wallet", imported: 'Imported', - error: 'Failed to import. Is the event valid?', + error: 'Failed to import. Please, make sure that the provided data is valid.', success: 'Success', do_import: 'Import', scan_qr: 'or scan QR code instead?', @@ -187,7 +187,7 @@ module.exports = { help: 'Under certain circumstances, you might be forced to disclose a ' + 'password. To keep your coins safe, BlueWallet can create another ' + - 'encrypted storage, with a different password. Under the pressure, ' + + 'encrypted storage, with a different password. Under pressure, ' + 'you can disclose this password to a 3rd party. If entered in ' + "BlueWallet, it will unlock new 'fake' storage. This will seem " + 'legit to a 3rd party, but will secretly keep your main storage ' + @@ -208,5 +208,7 @@ module.exports = { refill_lnd_balance: 'Refill Lightning wallet balance', refill: 'Refill', withdraw: 'Withdraw', + expired: 'Expired', + sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', }, }; diff --git a/loc/es.js b/loc/es.js index bc1da4dd0..164613634 100644 --- a/loc/es.js +++ b/loc/es.js @@ -210,5 +210,7 @@ module.exports = { refill_lnd_balance: 'Rellenar el balance de la billetera Lightning', refill: 'Rellenar', withdraw: 'Retirar', + expired: 'Expirado', + sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', }, }; diff --git a/loc/fr_FR.js b/loc/fr_FR.js new file mode 100644 index 000000000..b588109f3 --- /dev/null +++ b/loc/fr_FR.js @@ -0,0 +1,215 @@ +module.exports = { + _: { + storage_is_encrypted: 'L\'espace de stockage est chiffré. Mot de passe requis pour le déchiffrer.', + enter_password: 'Saisir mot de passe', + bad_password: 'Mauvais mot de passe, ré-essayer', + months_ago: 'mois', + days_ago: 'jours', + hours_ago: 'heures', + minutes_ago: 'minutes', + never: 'jamais', + }, + wallets: { + select_wallet: 'Choix du portefeuille', + options: 'options', + list: { + app_name: 'Blue Wallet', + title: 'portefeuilles', + header: 'Un portefeuille represente une paire de clées (publique/privée) et une adresse que vous pouvez partager pour recevoir des transactions.', + add: 'Ajouter un portefeuille', + create_a_wallet: 'Créer un portefeuille', + create_a_wallet1: "C\'est gratuit et vous pouvez en créer", + create_a_wallet2: 'autant que vous souhaitez', + latest_transaction: 'dernière transaction', + empty_txs1: 'Vos transactions apparaîtront ici,', + empty_txs2: 'Aucune pour le moment', + tap_here_to_buy: 'Cliquez ici pour acheter du Bitcoin', + }, + reorder: { + title: 'Trier vos portefeuilles', + }, + add: { + title: 'ajouter un portefeuille', + description: + 'Vous pouvez soit scanner et importer un paper wallet (au format WIF - Wallet Import Format), ou créer un nouveau portefeuille. Compatible avec Segwit par defaut.', + scan: 'Scanner', + create: 'Créer', + label_new_segwit: 'Nouveau SegWit', + label_new_lightning: 'Nouveau Lightning', + wallet_name: 'nom du portefeuille', + wallet_type: 'type', + or: 'ou', + import_wallet: 'Importer un portefeuille', + imported: 'Importé', + coming_soon: 'Bientôt', + lightning: 'Lightning', + bitcoin: 'Bitcoin', + }, + details: { + title: 'Portefeuille', + address: 'Adresse', + type: 'Type', + label: 'Libelé', + destination: 'destination', + description: 'description', + are_you_sure: 'Êtes vous sur?', + yes_delete: 'Oui, supprimer', + no_cancel: 'Non, annuler', + delete: 'Supprimer', + save: 'Sauvegarder', + delete_this_wallet: 'Supprimer ce portefeuille', + export_backup: 'Exporter / sauvegarder', + buy_bitcoin: 'Acheter du Bitcoin', + show_xpub: 'Afficher XPUB du portefeuille', + }, + export: { + title: 'export du portefeuille', + }, + xpub: { + title: 'XPUB portefeuille', + copiedToClipboard: 'Copié dans le presse-papiers.', + }, + import: { + title: 'importer', + explanation: + "Write here your mnemonic, private key, WIF, or anything you've got. BlueWallet will do its best to guess the correct format and import your wallet", + imported: 'Importé', + error: 'Échec de l\'import. Merci, de vérifier que les données saisies sont valides.', + success: 'Succès', + do_import: 'Importer', + scan_qr: 'ou scaner un QR code', + }, + scanQrWif: { + go_back: 'Retour', + cancel: 'Annuler', + decoding: 'Déchiffrage', + input_password: 'Saisir mot de passe', + password_explain: 'Ceci est une clée privée chiffrée avec BIP38', + bad_password: 'Mauvais mot de passe', + wallet_already_exists: 'Ce portefeuille existe déjà', + bad_wif: 'Mauvais WIF', + imported_wif: 'WIF Importé', + with_address: ' avec adresse ', + imported_segwit: 'SegWit Importé', + imported_legacy: 'Legacy Importé', + imported_watchonly: 'Monitoring Importé', + }, + }, + transactions: { + list: { + tabBarLabel: 'Transactions', + title: 'transactions', + description: 'Une liste des transactions entrentes et sortantes de vos portefeuilles', + conf: 'conf', + }, + details: { + title: 'Transaction', + from: 'De', + to: 'À', + copy: 'Copier', + transaction_details: 'Détails de la transaction', + show_in_block_explorer: 'Afficher dans le "block explorer"', + }, + }, + send: { + header: 'Envoyer', + details: { + title: 'créer une transaction', + amount_field_is_not_valid: 'Champ montant invalide', + fee_field_is_not_valid: 'Champ frais invalide', + address_field_is_not_valid: 'Champ adresse invalide', + total_exceeds_balance: 'Le montant à envoyer excède le montant disponible.', + create_tx_error: 'There was an error creating the transaction. Please, make sure the address is valid.', + address: 'adresse', + amount_placeholder: 'montant à envoyer (en BTC)', + fee_placeholder: 'plus frais de transaction (en BTC)', + note_placeholder: 'note (optionnelle)', + cancel: 'Annuler', + scan: 'Scanner', + send: 'Envoyer', + create: 'Créer', + remaining_balance: 'Balance restante', + }, + confirm: { + header: 'Confirmer', + sendNow: 'Envoyer maintenant', + }, + success: { + done: 'Terminé', + }, + create: { + details: 'Details', + title: 'créer une transaction', + error: 'Erreur creating transaction. Invalid address or send amount?', + go_back: 'Retour', + this_is_hex: 'This is transaction hex, signed and ready to be broadcast to the network.', + to: 'À', + amount: 'Montant', + fee: 'Frais', + tx_size: 'Taille de la Transaction (TX size)', + satoshi_per_byte: 'Satoshi par byte', + memo: 'Memo', + broadcast: 'Broadcast', + not_enough_fee: 'Frais insufisants. Veuillez augmenter les frais', + }, + }, + receive: { + header: 'Recevoir', + details: { + title: 'Partager cette adresse avec le destinataire', + share: 'partager', + copiedToClipboard: 'Copier dans le presse-papiers.', + label: 'Description', + setAmount: 'Revevoir avec montant', + }, + }, + buyBitcoin: { + header: 'Acheter du Bitcoin', + tap_your_address: 'Cliquez votre adresse pour la copier:', + copied: 'Copié dans le presse-papiers!', + }, + settings: { + header: 'réglages', + plausible_deniability: 'Déni plausible...', + storage_not_encrypted: 'Stockage: non chiffré', + storage_encrypted: 'Stockage: chiffré', + password: 'Mot de passe', + password_explain: 'Créer le mot de passe utilisé pour déchiffrer l\'espace de stockage principal', + retype_password: 'Re-saisir votre mot de passe', + passwords_do_not_match: 'Les mots de passe ne correspondent pas', + encrypt_storage: 'Chiffrer le stockage', + about: 'À propos', + language: 'Langue', + currency: 'Devise', + }, + plausibledeniability: { + title: 'Déni plausible', + help: + 'Dans certaines circonstances, vous serez peut-être forcé par un tiers à communiquer ' + + 'votre mot de passe. Pour protéger vos biens, BlueWallet permet de créer un autre ' + + 'espace de stockage, avec un mot de passe différent. Sous la contrainte, ' + + 'vous pourrez divulger ce mot de passe au tier. Quand il est saisi ' + + "BlueWallet, débloquera se 'faux' espace de stockage. Le tiers pourra " + + 'confondre ces données avec des données légitimes, votre espace de stockage ' + + 'principal restera sécurisé et hors d\'atteinte.', + help2: 'New storage will be fully functional, and you can store some ' + 'minimum amounts there so it looks more believable.', + create_fake_storage: 'Créer un faux espace de stockage chiffré', + go_back: 'Retour', + create_password: 'Créer un mot de passe', + create_password_explanation: 'Le mot de passe pour le faux espace de stockage ne doit pas être le même que celui du stockage principal', + password_should_not_match: 'Le mot de passe pour le faux espace de stockage ne doit pas être le même que celui du stockage principal', + retype_password: 'Confirmation du mot de passe', + passwords_do_not_match: 'Vos mot de passe ne sont pas identiques, veillez ré-essayer', + success: 'Succès', + }, + lnd: { + title: 'gérer vos fonds', + choose_source_wallet: 'Choisir un portefeuille source', + refill_lnd_balance: 'Déposer des fonds dans votre portfeuille Lightning', + refill: 'Déposer des fonds', + withdraw: 'Retirer des fonds', + expired: 'Expiré', + sameWalletAsInvoiceError: 'Vous ne pouvez pas payer une facture avec le même portefeuille utilisé pour la créer.', + }, + }; + \ No newline at end of file diff --git a/loc/index.js b/loc/index.js index f75533b7a..7e46ae5d8 100644 --- a/loc/index.js +++ b/loc/index.js @@ -21,7 +21,19 @@ let strings; locale = locale.split('-'); locale = locale[0]; console.log('current locale:', locale); - if (locale === 'en' || locale === 'ru' || locale === 'ua' || locale === 'es' || locale === 'pt-br' || locale === 'pt-pt' || locale === 'de-de') { + if ( + locale === 'en' || + locale === 'ru' || + locale === 'ua' || + locale === 'es' || + locale === 'fr-fr' || + locale === 'pt-br' || + locale === 'pt-pt' || + locale === 'de-de' || + locale === 'cs-cz' || + locale === 'th-th' || + locale === 'nl-nl' + ) { locale = locale.replace('-', '_'); strings.setLanguage(locale); } else { @@ -38,7 +50,11 @@ strings = new Localization({ pt_pt: require('./pt_PT.js'), es: require('./es.js'), ua: require('./ua.js'), - de_de: require('.de_DE.js') + de_de: require('./de_DE.js'), + cs_cz: require('./cs_CZ.js'), + th_th: require('./th_TH.js'), + nl_nl: require('./nl_NL.js'), + fr_fr: require('./fr_FR.js'), }); strings.saveLanguage = lang => AsyncStorage.setItem(AppStorage.LANG, lang); @@ -90,7 +106,7 @@ strings.formatBalance = (balance, toUnit) => { return balance + ' ' + BitcoinUnit.BTC; } else if (toUnit === BitcoinUnit.SATS) { const value = new BigNumber(balance).multipliedBy(100000000); - return value.toString() + ' ' + BitcoinUnit.SATS; + return new Intl.NumberFormat().format(value.toString()) + ' ' + BitcoinUnit.SATS; } else if (toUnit === BitcoinUnit.LOCAL_CURRENCY) { return currency.BTCToLocalCurrency(balance); } @@ -111,12 +127,12 @@ strings.formatBalanceWithoutSuffix = (balance, toUnit) => { const value = new BigNumber(balance).dividedBy(100000000).toFixed(8); return removeTrailingZeros(value); } else if (toUnit === BitcoinUnit.SATS) { - return balance; + return new Intl.NumberFormat().format(balance); } else if (toUnit === BitcoinUnit.LOCAL_CURRENCY) { return currency.satoshiToLocalCurrency(balance); } } - return balance; + return balance.toString(); }; module.exports = strings; diff --git a/loc/nl_NL.js b/loc/nl_NL.js new file mode 100644 index 000000000..fece8cae0 --- /dev/null +++ b/loc/nl_NL.js @@ -0,0 +1,216 @@ +module.exports = { + _: { + storage_is_encrypted: 'Uw opslag is versleuteld. Wachtwoord is vereist om het te ontcijferen', + enter_password: 'Voer wachtwoord in', + bad_password: 'Verkeerd wachtwoord, probeer opnieuw', + months_ago: 'maanden geleden', + days_ago: 'dagen geleden', + hours_ago: 'uur geleden', + minutes_ago: 'minuten geleden', + never: 'nooit', + }, + wallets: { + select_wallet: 'Selecteer portemonnee', + options: 'opties', + list: { + app_name: 'Blue Wallet', + title: 'portemonnees', + header: 'Een portemonnee vertegenwoordigt een geheime (privésleutel) en een adres' + 'dat u kunt delen om munten te ontvangen.', + add: 'Portemonnee toevoegen', + create_a_wallet: 'Portemonnee aanmaken', + create_a_wallet1: 'Het is gratis en u kunt er', + create_a_wallet2: 'zoveel maken als u wilt', + latest_transaction: 'laatste transactie', + empty_txs1: 'Uw transacties verschijnen hier,', + empty_txs2: 'geen transacties op dit moment', + tap_here_to_buy: 'Klik hier om Bitcoin te kopen', + }, + reorder: { + title: 'Portemonnees opnieuw ordenen', + }, + add: { + title: 'portemonnee toevoegen', + description: + 'U kunt een back-up papieren portemonnee scannen (in WIF - Wallet Import Format) of een nieuwe portemonnee maken. Segwit-wallets worden standaard ondersteund.', + scan: 'Scannen', + create: 'Aanmaken', + label_new_segwit: 'Nieuwe SegWit', + label_new_lightning: 'Nieuwe Lightning', + wallet_name: 'portemonnee naam', + wallet_type: 'type', + or: 'of', + import_wallet: 'Portemonnee importeren', + imported: 'Geïmporteerd', + coming_soon: 'Komt binnenkort', + lightning: 'Lightning', + bitcoin: 'Bitcoin', + }, + details: { + title: 'Portemonnee', + address: 'Adres', + type: 'Type', + label: 'Label', + destination: 'bestemming', + description: 'beschrijving', + are_you_sure: 'Weet u het zeker?', + yes_delete: 'Ja, verwijderen', + no_cancel: 'Nee, annuleren', + delete: 'Verwijderen', + save: 'Opslaan', + delete_this_wallet: 'Verwijder deze portemonnee', + export_backup: 'Exporteren / back-up maken', + buy_bitcoin: 'Koop Bitcoin', + show_xpub: 'Toon portemonnee XPUB', + }, + export: { + title: 'portemonnee exporteren', + }, + xpub: { + title: 'portemonnee XPUB', + copiedToClipboard: 'Gekopieerd naar het klembord.', + }, + import: { + title: 'importeren', + explanation: + 'Schrijf hier uw ezelsbruggetje, privésleutel, WIF, of een ander formaat. BlueWallet zal zijn best doen om het juiste formaat te raden en uw portemonnee te importeren', + imported: 'Geïmporteerd', + error: 'Importeren mislukt. Zorg ervoor dat de verstrekte gegevens geldig zijn.', + success: 'Succes', + do_import: 'Importeren', + scan_qr: 'of QR-code scannen?', + }, + scanQrWif: { + go_back: 'Ga terug', + cancel: 'Annuleren', + decoding: 'Decoderen', + input_password: 'Voer wachtwoord in', + password_explain: 'Dit is een BIP38-gecodeerde privésleutel', + bad_password: 'Verkeerd wachtwoord', + wallet_already_exists: "Zo'n portemonnee bestaat al", + bad_wif: 'Verkeerde WIF', + imported_wif: 'WIF geïmporteerd ', + with_address: ' met adres ', + imported_segwit: 'SegWit geïmporteerd', + imported_legacy: 'Legacy geïmporteerd', + imported_watchonly: 'Watch-only geïmporteerd', + }, + }, + transactions: { + list: { + tabBarLabel: 'Transacties', + title: 'transacties', + description: 'Een lijst met ingaande of uitgaande transacties van uw portemonnee', + conf: 'conf', + }, + details: { + title: 'Transacties', + from: 'Invoer', + to: 'Uitgang', + copy: 'Kopiëren', + transaction_details: 'Transactie details', + show_in_block_explorer: 'Weergeven in blokverkenner', + }, + }, + send: { + header: 'Vertuur', + details: { + title: 'transacties aanmaken', + amount_field_is_not_valid: 'Bedrag veld is niet geldig', + fee_field_is_not_valid: 'Tarief is niet geldig', + address_field_is_not_valid: 'Adresveld is niet geldig', + total_exceeds_balance: 'Het verzendingsbedrag overschrijdt het beschikbare saldo.', + create_tx_error: 'Er is een fout opgetreden bij het maken van de transactie. Zorg ervoor dat het adres geldig is.', + address: 'adres', + amount_placeholder: 'te verzenden bedrag (in BTC)', + fee_placeholder: 'plus transactie vergoeding (in BTC)', + note_placeholder: 'notitie voor mezelf', + cancel: 'Annuleren', + scan: 'Scannen', + send: 'Verzenden', + create: 'Aanmaken', + remaining_balance: 'Resterende saldo', + }, + confirm: { + header: 'Bevestig', + sendNow: 'Nu verzenden', + }, + success: { + done: 'Klaar', + }, + create: { + details: 'Details', + title: 'transactie aanmaken', + error: 'Fout bij het maken van transactie. Ongeldig adres of bedrag?', + go_back: 'Ga terug', + this_is_hex: 'Dit is de transactie-hex, ondertekend en klaar om op het netwerk te worden uitgezonden.', + to: 'Naar', + amount: 'Bedrag', + fee: 'Vergoeding', + tx_size: 'TX grootte', + satoshi_per_byte: 'Satoshi per byte', + memo: 'Memo', + broadcast: 'Uitzenden', + not_enough_fee: 'Niet genoeg vergoeding. Verhoog de vergoeding', + }, + }, + receive: { + header: 'Ontvang', + details: { + title: 'Deel dit adres met betaler', + share: 'delen', + copiedToClipboard: 'Gekopieerd naar het klembord.', + label: 'Omschrijving', + setAmount: 'Ontvang met bedrag', + }, + }, + buyBitcoin: { + header: 'Koop Bitcoin', + tap_your_address: 'Tik op uw adres om het naar het klembord te kopiëren:', + copied: 'Gekopieerd naar het klembord!', + }, + settings: { + header: 'instellingen', + plausible_deniability: 'Plausibele ontkenning...', + storage_not_encrypted: 'Opslag: niet versleuteld', + storage_encrypted: 'Opslag: versleuteld', + password: 'Wachtwoord', + password_explain: 'Maak een wachtwoord aan dat u wilt gebruiken om de opslag te versleutelen', + retype_password: 'Geef nogmaals het wachtwoord', + passwords_do_not_match: 'Wachtwoorden komen niet overeen', + encrypt_storage: 'Versleutel opslag', + about: 'Over', + language: 'Taal', + currency: 'Valuta', + }, + plausibledeniability: { + title: 'Plausibele ontkenning', + help: + 'Onder bepaalde omstandigheden kunt u worden gedwongen om uw' + + ' wachtwoord te onthullen. Om uw munten veilig te houden, kan ' + + 'BlueWallet nog een versleutelde opslag aanmaken, met een ander ' + + 'wachtwoord. Onder druk kunt u dit wachtwoord bekendmaken aan ' + + 'de derde partij. Indien ingevoerd in BlueWallet, zal het nieuwe ' + + "nep'-opslagruimte worden ontgrendeld. Dit lijkt legitiem voor de " + + 'derde partij, maar zal uw hoofdopslag met munten niet bekend maken ' + + 'aan de derde partij', + help2: + 'De nieuwe opslag zal volledig functioneel zijn en u kunt er ' + 'een minimum aantal munten opslaan zodat het geloofwaardig lijkt.', + create_fake_storage: 'Nep versleutelde opslag aanmaken', + go_back: 'Ga terug', + create_password: 'Wachtwoord aanmaken', + create_password_explanation: 'Wachtwoord voor nep-opslag hoort niet overeen te komen met wachtwoord voor uw hoofdopslag', + password_should_not_match: 'Wachtwoord voor nep-opslag hoort niet overeen te komen met wachtwoord voor uw hoofdopslag', + retype_password: 'Herhaal wachtwoord', + passwords_do_not_match: 'Wachtwoorden komen niet overeen, probeer het opnieuw', + success: 'Succes', + }, + lnd: { + title: 'fondsen beheren', + choose_source_wallet: 'Kies een bron portemonnee', + refill_lnd_balance: 'Vul Lightning-portemonneesaldo bij', + refill: 'Bijvullen', + withdraw: 'Opvragen', + expired: 'Verlopen', + sameWalletAsInvoiceError: 'U kunt geen factuur betalen met dezelfde portemonnee die is gebruikt om de factuur te maken.', + }, +}; diff --git a/loc/pt_BR.js b/loc/pt_BR.js index 6d6446c53..d57185f00 100644 --- a/loc/pt_BR.js +++ b/loc/pt_BR.js @@ -1,125 +1,125 @@ module.exports = { _: { - storage_is_encrypted: 'O armazenamento está encriptado. Uma password é necessaria para desencriptar', - enter_password: 'Inserir password', - bad_password: 'pasword errada, tentar novamente', + storage_is_encrypted: 'Os arquivos estão criptografados, é necessária uma senha', + enter_password: 'Inserir senha', + bad_password: 'Senha errada, tente outra vez', months_ago: 'meses atrás', days_ago: 'dias atrás', hours_ago: 'horas atrás', - minutes_ago: 'minutos', - never: 'nunca...', + minutes_ago: 'minutos atrás', + never: 'nunca', }, wallets: { - options: 'options', - select_wallet: 'Select Wallet', + options: 'opções', + select_wallet: 'Escolher carteira', list: { - tabBarLabel: 'Wallets', + tabBarLabel: 'Carteiras', app_name: 'Blue Wallet', - title: 'Wallets', - header: 'Uma wallet representa um par entre um segredo (chave privada) e um endereço' + 'que pode partilhar para receber Bitcoin.', + title: 'carteiras', + header: 'Uma carteira representa um par composto de uma chave privada e um endereço que você pode .', add: 'adicionar wallet', - create_a_wallet: 'Criar uma wallet', - create_a_wallet1: 'Gratuito e pode criar', - create_a_wallet2: ' quantas quiser', + create_a_wallet: 'Criar uma carteira', + create_a_wallet1: 'é grátis e você pode criar', + create_a_wallet2: 'quantas você quiser', latest_transaction: 'última transação', - empty_txs1: 'Suas transações aparecerão aqui', - empty_txs2: 'nenhuma de momento', - tap_here_to_buy: 'Tap here to buy Bitcoin', + empty_txs1: 'Suas transações aparecerão aqui,', + empty_txs2: 'nenhuma no momento', + tap_here_to_buy: 'Toque aqui para comprar Bitcoin', }, reorder: { - title: 'Reorder Wallets', + title: 'Reordenar carteiras', }, add: { - title: 'Adicionar Wallet', + title: 'criando carteira', description: - 'Pode fazer um scaneamento de um backup de uma wallet em papel (em WIF - Wallet Import Format), ou criar uma nova wallet. Segwit suportado por defeito.', - scan: 'Scanear', + 'Você pode ler o backup de uma carteira (em WIF - Wallet Import Format) ou criar uma nova. O padrão é criar uma carteira SegWit.', + scan: 'Ler backup', create: 'Criar', - label_new_segwit: 'Novo SegWit', - label_new_lightning: 'Novo Lightning', - wallet_name: 'Nome', - wallet_type: 'Tipo', + label_new_segwit: 'Nova carteira SegWit', + label_new_lightning: 'Nova carteira Lightning', + wallet_name: 'nome', + wallet_type: 'tipo', or: 'ou', - import_wallet: 'Importar wallet', + import_wallet: 'Importar carteira', imported: 'Importado', - coming_soon: 'Brevemente', + coming_soon: 'Em breve', lightning: 'Lightning', bitcoin: 'Bitcoin', }, details: { - title: 'wallet', + title: 'Carteira', address: 'Endereço', type: 'Tipo', - destination: 'destination', - description: 'description', + destination: 'destino', + description: 'descrição', label: 'Nome', - are_you_sure: 'Tem a certeza?', - yes_delete: 'Sim, eliminar', + are_you_sure: 'Tem certeza?', + yes_delete: 'Sim, apagar', no_cancel: 'Não, cancelar', - delete_this_wallet: 'Apagar esta wallet', + delete_this_wallet: 'Apagar esta carteira', export_backup: 'Exportar / backup', - buy_bitcoin: 'Buy Bitcoin', - show_xpub: 'Show wallet XPUB', - delete: 'Delete', - save: 'Save', + buy_bitcoin: 'Comprar Bitcoin', + show_xpub: 'Ver XPUB', + delete: 'Apagar', + save: 'Salvar', }, export: { - title: 'Exportar Wallet', + title: 'Exportar carteira', }, xpub: { - title: 'wallet XPUB', - copiedToClipboard: 'copiado para clip board', + title: 'XPUB', + copiedToClipboard: 'Copiado para a área de transferência', }, import: { title: 'importar', explanation: - 'Escreva aqui sua frase mnemônica, chave privada, WIF, etc. Vamos fazer nosso melhor para importat a sua wallet em qualquer formato', + 'Escreva aqui sua frase mnemônica, chave privada, WIF, ou o que você tiver. Faremos nosso melhor para adivinhar o formato e importat sua carteira', imported: 'Importada', - error: 'Falhou. é um formato válido?', + error: 'Erro. Por favor, confira se o formato que você passou é válido.', success: 'Sucesso', do_import: 'Importar', - scan_qr: 'ou scan um QR code?', + scan_qr: 'ou ler um código QR?', }, scanQrWif: { go_back: 'Voltar', cancel: 'Cancelar', - decoding: 'Descodificar', - input_password: 'Inserir password', - password_explain: 'Isto é um BIP38 chave privada encriptada', - bad_password: 'Password errada', - wallet_already_exists: 'Esta wallet já existe', + decoding: 'Decodificar', + input_password: 'Inserir senha', + password_explain: 'Isto é um chave privada criptografada BIP38', + bad_password: 'Senha errada', + wallet_already_exists: 'Esta carteira já existe', bad_wif: 'WIF errado', - imported_wif: 'WIF transferido ', + imported_wif: 'WIF importado ', with_address: ' com endereço ', - imported_segwit: 'SegWit transferido', - imported_legacy: 'Legacy transferido', - imported_watchonly: 'Watch-only importada', + imported_segwit: 'Carteira SegWit importada', + imported_legacy: 'Carteira antiga importada', + imported_watchonly: 'Carteira somente-leitura importada', }, }, transactions: { list: { tabBarLabel: 'Transações', title: 'Transações', - description: 'Uma lista de transações feitas ou recebidas nas suas wallets', + description: 'Uma lista de transações feitas ou recebidas nas suas carteiras', conf: 'conf', }, details: { - title: 'transação', + title: 'Transação', from: 'De', to: 'Para', copy: 'Copiar', - transaction_details: 'Transaction details', - show_in_block_explorer: 'Show in block explorer', + transaction_details: 'Detalhes', + show_in_block_explorer: 'Mostrar num navegador', }, }, send: { header: 'Enviar', confirm: { - header: 'Confirm', - sendNow: 'Send now', + header: 'Confirmar', + sendNow: 'Enviar agora', }, success: { - done: 'Done', + done: 'Enviado', }, details: { title: 'Criar Transacção', @@ -134,53 +134,53 @@ module.exports = { cancel: 'Cancelar', scan: 'Scanear', create: 'Criar', - address: 'Address', - total_exceeds_balance: 'The total amount exceeds balance', + address: 'Endereço', + total_exceeds_balance: 'Valor total excede o saldo disponível', send: 'Send', remaining_balance: 'Saldo restante', }, create: { title: 'Criar Transacção', - details: 'Details', - error: 'Erro ao criar transação. Endereço inválido ou quantia?', + details: 'Detalhes', + error: 'Erro ao criar transação. Endereço ou valor inválidos?', go_back: 'Voltar', - this_is_hex: 'Este é o hex da transação, assinado e pronto para ser difundido para a network. Continuar?', + this_is_hex: 'Este é o hex da transação, assinado e pronto para ser divulgado para o mundo. Continuar?', to: 'Para', - amount: 'Quantia', + amount: 'Valor', fee: 'Taxa', - tx_size: 'Tamanho TX', - satoshi_per_byte: 'satoshiPerByte', - memo: 'Nota pessoal', - broadcast: 'Difundir', - not_enough_fee: 'Taxa demasiado baixa. Aumente a taxa', + tx_size: 'Tamanho', + satoshi_per_byte: 'satoshis por byte', + memo: 'Nota', + broadcast: 'Divulgar', + not_enough_fee: 'Taxa muito baixa. Aumente a taxa', }, }, receive: { - header: 'receber', + header: 'Receber', details: { - title: 'Partilhar este endereço com o pagador', - share: 'partilhar', - copiedToClipboard: 'copiado para clip board', - label: 'Description', - setAmount: 'Receive with amount', + title: 'Envie este endereço para o pagador', + share: 'Compartilhar', + copiedToClipboard: 'Copiado para a área de trabalho', + label: 'Descrição', + setAmount: 'Valor a receber', }, }, buyBitcoin: { - header: 'Buy Bitcoin', - tap_your_address: 'Tap your address to copy it to clipboard:', - copied: 'Copied to Clipboard!', + header: 'Comprar Bitcoin', + tap_your_address: 'Toque seu endereço para copiá-lo para a área de transferência:', + copied: 'Copiado!', }, settings: { - tabBarLabel: 'Definições', + tabBarLabel: 'preferências', header: 'definições', plausible_deniability: 'Negação plausível...', - storage_not_encrypted: 'Armazenamento: não encriptado', - storage_encrypted: 'Armazenamento: encriptado', - password: 'Password', - password_explain: 'Definir a password para desencriptar o armazenamento', - retype_password: 'Inserir password novamente', - passwords_do_not_match: 'Passwords não coincidem', - encrypt_storage: 'Encriptar', + storage_not_encrypted: 'Arquivos: não criptografados', + storage_encrypted: 'Arquivos: criptografados', + password: 'Senha', + password_explain: 'Definir a senha para descriptografar os arquivos', + retype_password: 'Inserir senha novamente', + passwords_do_not_match: 'Senhas não coincidem', + encrypt_storage: 'Criptografar', about: 'Sobre', language: 'Idioma', currency: 'Moeda', @@ -188,28 +188,30 @@ module.exports = { plausibledeniability: { title: 'Negação plausível', help: - 'Em algumas circunstâncias, pode ser forçado a relevar uma ' + - 'password. Para manter as suas moedas seguras, A BlueWallet pode criar outro ' + - 'armazenamento encriptado, com uma password diferente. Sobre pressão, ' + - 'pode revelar esta password a um terceiro. Se inserida na ' + - 'BlueWallet, esta vai abrir um armazenamento "falso". Que vai parecer ' + - 'legítimo a um terceiro, mas que secretamente vai manter o seu armazenamento principal ' + - 'com as moedas em segurança.', - help2: 'Este novo armazenamento é completamente funcional, e pode guardar ' + 'um valor minímo para parecer mais real.', - create_fake_storage: 'Criar armazenamento encriptado FALSO', + 'Em algumas circunstâncias, você pode ser forçado a revelar uma ' + + 'senha. Para manter seus bitcoins seguros, A BlueWallet pode criar ' + + 'uma senha alternativa. Sob pressão, você pode revelar essa senha ao ' + + 'invés da senha principal. Quando inserida na BlueWallet, esta abrirá ' + + 'uma interface falsa, que parecerá legítima a um terceiro, enquanto ' + + 'suas carteiras originais continuarão à salvo em segredo.', + help2: + 'Essa nova interface é completamente funcional e você pode inclusive ' + 'manter nele um valor minímo para que pareça mais real.', + create_fake_storage: 'Criar armazenamento criptografada falsa', go_back: 'Voltar', - create_password: 'Criar password', - create_password_explanation: 'Password para armazenamento FALSO não deve coincidir com o principal', - password_should_not_match: 'Password para armazenamento FALSO não deve coincidir com o principal', - retype_password: 'Inserir password novamente', - passwords_do_not_match: 'Passwords não coincidem, tente novamente', + create_password: 'Criar senha', + create_password_explanation: 'A senha para a interface falsa não deve coincidir com a principal', + password_should_not_match: 'A senha para a interface falsa não deve coincidir com a principal', + retype_password: 'Inserir senha novamente', + passwords_do_not_match: 'Senhas não coincidem, tente novamente', success: 'Sucesso', }, lnd: { - title: 'gerenciar fundos', - choose_source_wallet: 'Escolha a sua wallet', - refill_lnd_balance: 'Carregar o saldo da Lightning wallet', - refill: 'Carregar', - withdraw: 'Transferir', + title: 'manejar fundos', + choose_source_wallet: 'Escolha a carteira de origem', + refill_lnd_balance: 'Recarregar a carteira Lightning', + refill: 'Recarregar', + withdraw: 'Sacar', + expired: 'Vencido', + sameWalletAsInvoiceError: 'Você não pode pagar uma fatura com a mesma carteira que a criou.', }, }; diff --git a/loc/pt_PT.js b/loc/pt_PT.js index cb52920f6..9e77c6836 100644 --- a/loc/pt_PT.js +++ b/loc/pt_PT.js @@ -210,5 +210,7 @@ module.exports = { refill_lnd_balance: 'Carregar o saldo da Lightning wallet', refill: 'Carregar', withdraw: 'Transferir', + expired: 'Expired', + sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', }, }; diff --git a/loc/ru.js b/loc/ru.js index 72b2bb808..eeeac1c9e 100644 --- a/loc/ru.js +++ b/loc/ru.js @@ -211,5 +211,7 @@ module.exports = { refill_lnd_balance: 'Пополнить баланс Lightning кошелька', refill: 'Пополнить', withdraw: 'Вывести', + expired: 'Expired', + sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', }, }; diff --git a/loc/th_TH.js b/loc/th_TH.js new file mode 100644 index 000000000..c83c217a8 --- /dev/null +++ b/loc/th_TH.js @@ -0,0 +1,213 @@ +module.exports = { + _: { + storage_is_encrypted: 'ที่เก็บข้อมูลของคุณถูกเข้ารหัส. ต้องการรหัสผ่านเพื่อถอดรหัส', + enter_password: 'กรุณาใส่รหัสผ่าน', + bad_password: 'รหัสผ่านไม่ถูกต้อง กรุณาใส่รหัสผ่านอีกครั้ง', + months_ago: 'เดือนที่แล้ว', + days_ago: 'วันที่แล้ว', + hours_ago: 'ชั่วโมงที่แล้ว', + minutes_ago: 'นาทีที่แล้ว', + never: 'ไม่เคย', + }, + wallets: { + select_wallet: 'เลือกกระเป๋าสตางค์', + options: 'ทางเลือก', + list: { + app_name: 'บูลวอลเล็ต', + title: 'กระเป๋าสตางค์', + header: 'กระเป๋าสตางค์คือที่เก็บไพร์เวทคีย์และแอดเดรส' + 'ที่คุณสามารถแชร์เพื่อโอนรับเหรียญ.', + add: 'เพิ่มกระเป๋าสตางค์', + create_a_wallet: 'สร้างกระเป๋าสตางค์', + create_a_wallet1: 'ไม่มีค่าใช้จ่าย และคุณสามารถสร้างกระเป๋าสตางค์', + create_a_wallet2: 'ได้มากเท่าที่ต้องการ', + latest_transaction: 'ธุรกรรมล่าสุด', + empty_txs1: 'ธุรกรรมจะปรากฏที่นี่,', + empty_txs2: 'ไม่มี ณ ขณะนี้', + tap_here_to_buy: 'กดที่นี่เพื่อซื้อบิตคอยน์', + }, + reorder: { + title: 'เปลี่ยนลำดับกระเป๋าสตางค์', + }, + add: { + title: 'เพิ่มกระเป๋าสตางค์', + description: + 'คุณสามรถสแกนกระเป๋าสตางค์กระดาษ(ในรูปแบบ WIF - Wallet Import Format), หรือสร้างกระเป๋าสตางค์ใหม่. กระเป๋าสตางค์ใหม่จะใช้ Segwit โดยอัตโนมัติ.', + scan: 'สแกน', + create: 'สร้าง', + label_new_segwit: 'SegWit ใหม่', + label_new_lightning: 'ไลท์นิงใหม่', + wallet_name: 'ชื่อกระเป๋าสตางค์', + wallet_type: 'ชนิด', + or: 'หรือ', + import_wallet: 'นำเข้ากระเป๋าสตางค์', + imported: 'นำเข้าแล้ว', + coming_soon: 'เร็วๆนี้', + lightning: 'ไลท์นิง', + bitcoin: 'บิตคอยน์', + }, + details: { + title: 'กระเป๋าสตางค์', + address: 'แอดเดรส', + type: 'ชนิด', + label: 'ป้าย', + destination: 'เป้าหมาย', + description: 'คำอธิบาย', + are_you_sure: 'คุณแน่ใจหรือไม่?', + yes_delete: 'ใช่, ลบเลย', + no_cancel: 'ไม่ใช่, ยกเลิก', + delete: 'ลบ', + save: 'เก็บ', + delete_this_wallet: 'ลบกระเป๋าสตางค์อันนี้', + export_backup: 'ส่งออก / สำรอง', + buy_bitcoin: 'ซื้อบิตคอยน์', + show_xpub: 'แสดง XPUB ของกระเป๋าสตางค์', + }, + export: { + title: 'ส่งออกกระเป๋าสตางค์', + }, + xpub: { + title: 'XPUB ของกระเป๋าสตางค์', + copiedToClipboard: 'ก๊อปปี้ไปที่คลิปบอร์ดแล้ว.', + }, + import: { + title: 'นำเข้า', + explanation: 'บันทึกนีโมนิค(สิ่งที่ช่วยให้จำได้), ไพร์เวทคีย์, WIF, และทุกๆอย่าง. บูลวอลเล็ทจะพยายามนำเข้ากระเป๋าสตางค์ของคุณ', + imported: 'นำเข้าแล้ว', + error: 'ไม่สามารถนำเข้าได้. กรุณาตรวจสอบข้อมูลให้ถูกต้อง.', + success: 'สำเร็จ', + do_import: 'นำเข้า', + scan_qr: 'หรือสแกนรหัสคิวอาร์แทน?', + }, + scanQrWif: { + go_back: 'กลับ', + cancel: 'ยกเลิก', + decoding: 'กำลังถอดรหัส', + input_password: 'ใส่รหัสผ่าน', + password_explain: 'นี่คือไพร์เวทคีย์ที่เข้ารหัสแบบ BIP38', + bad_password: 'รหัสไม่ถูกต้อง', + wallet_already_exists: 'กระเป๋าสตางค์นี้มีอยู่แล้ว', + bad_wif: 'WIF ไม่ถูกต้อง', + imported_wif: 'WIF ที่นำเข้า', + with_address: ' ด้วยแอดเดรส ', + imported_segwit: 'SegWit ที่นำเข้า', + imported_legacy: 'Legacy ที่นำเข้า', + imported_watchonly: 'Watch-only ที่นำเข้า', + }, + }, + transactions: { + list: { + tabBarLabel: 'ธุรกรรม', + title: 'ธุรกรรม', + description: 'รายการธุรกรรมเข้าออกของกระเป๋าสตางค์ของคุณ', + conf: 'conf', + }, + details: { + title: 'ธุรกรรม', + from: 'อินพุท', + to: 'เอ้าพุท', + copy: 'ก๊อปปี้', + transaction_details: 'รายละเอียดธุรกรรม', + show_in_block_explorer: 'แสดงด้วย block explorer', + }, + }, + send: { + header: 'ส่ง', + details: { + title: 'สร้างธุรกรรม', + amount_field_is_not_valid: 'จำนวนเงินไม่ถูกต้อง', + fee_field_is_not_valid: 'ค่าธรรมเนียมไม่ถูกต้อง', + address_field_is_not_valid: 'แอดเดรสไม่ถูกต้อง', + total_exceeds_balance: 'จำนวนเงินที่จะส่งเกินเงินที่มี.', + create_tx_error: 'ไม่สามารถสร้างธุรกรรมได้. กรุณาตรวจสอบแอดเดรสให้ถูกต้อง.', + address: 'แอดเดรส', + amount_placeholder: 'จำนวนเงินที่ส่ง (หน่วย BTC)', + fee_placeholder: 'รวมค่าธรรมเนียม (หน่วย BTC)', + note_placeholder: 'หมายเหตุถึงตัวท่านเอง', + cancel: 'ยกเลิก', + scan: 'สแกน', + send: 'ส่ง', + create: 'สร้าง', + remaining_balance: 'ยอดคงเหลือ', + }, + confirm: { + header: 'ยืนยัน', + sendNow: 'ส่งเดี๋ยวนี้', + }, + success: { + done: 'สำเร็จ', + }, + create: { + details: 'รายละเอียด', + title: 'สร้างธุรกรรม', + error: 'ไม่สามารถสร้างธุรกรรมได้. แอดเดรสไม่ถูกต้อง หรือ จำนวนเงินไม่ถูกต้อง?', + go_back: 'กลับ', + this_is_hex: 'นี่คือ hex ของธุรกรรม, signed แล้วและพร้อมที่จะบรอดคาซท์ไปยังเน็ตเวิร์ค.', + to: 'ถึง', + amount: 'จำนวนเงิน', + fee: 'ค่าธรรมเนียม', + tx_size: 'ขนาดธุรกรรม', + satoshi_per_byte: 'ซาโตชิต่อไบท์', + memo: 'บันทึกช่วยจำ', + broadcast: 'บรอดคาซท์', + not_enough_fee: 'ค่าธรรมเนียมไม่เพียงพอ. กรุณาเพิ่มค่าธรรมเนียม', + }, + }, + receive: { + header: 'รับ', + details: { + title: 'แชร์แอดเดรสนี้กับผู้จ่าย', + share: 'แชร์', + copiedToClipboard: 'ก๊อปปี้ไปที่คลิปบอร์ดแล้ว.', + label: 'คำอธิบาย', + setAmount: 'รับด้วยจำนวน', + }, + }, + buyBitcoin: { + header: 'ซื้อบิตคอยน์', + tap_your_address: 'กดที่แอดเดรสของคุณเพื่อก๊อปปี้ไปยังคลิปบอร์ด:', + copied: 'ก๊อปปี้ไปที่คลิปบอร์ดแล้ว!', + }, + settings: { + header: 'ตั้งค่า', + plausible_deniability: 'การปฏิเสธที่เป็นไปได้...', + storage_not_encrypted: 'ที่เก็บข้อมูล: ยังไม่เข้ารหัส', + storage_encrypted: 'ที่เก็บข้อมูล: เข้ารหัสแล้ว', + password: 'รหัสผ่าน', + password_explain: 'สร้างรหัสผ่านที่จะใช้ในการเข้ารหัสที่เก็บข้อมูล', + retype_password: 'ใส่รหัสผ่านอีกครั้ง', + passwords_do_not_match: 'รหัสผ่านไม่ตรงกัน', + encrypt_storage: 'เข้ารหัสที่เก็บข้อมูล', + about: 'เกี่ยวกับ', + language: 'ภาษา', + currency: 'เงินตรา', + }, + plausibledeniability: { + title: 'การปฏิเสธที่เป็นไปได้', + help: + 'ภายใต้บางสถานการ์ณ, คุณอาจจะจำเป็นต้องเปิดเผย' + + 'รหัสผ่าน. เพื่อเก็บเหรียญให้ปลอดถัย บูลวอลเล็ทสามารถสร้างที่เก็บข้อมูล' + + 'อีกแห่งหนึ่งโดยใช้รหัสผ่านคนละอัน. ภายใต้สถานการ์ณที่จำเป็น ' + + 'คุณสามารถเปิดเลยรหัสผ่านนี้กับบุคคลที่สาม. และเมื่อใส่รหัสผ่านนี้ใน ' + + 'บลูวอลเล็ท ที่เก็บข้อมูลเทียมจะถูกเปิด. และ' + + 'น่าจะเป็นที่ยอมรับได้ต่อบุคลที่สาม, วิธีนี้จะทำให้ที่เก็บข้อมูลหลักมีความปลอดภัย' + + 'และเป็นความลับ.', + help2: 'ที่เก็บข้อมูลอันใหม่จะทำงานได้สมบูรณ์ และคุณสามารถเก็บจำนวนเงินขั้นต่ำได้ ' + 'โดยที่มีความน่าเชื่อถือ.', + create_fake_storage: 'สร้างที่เก็บข้อมูลเทียม', + go_back: 'กลับ', + create_password: 'สร้างรหัสผ่าน', + create_password_explanation: 'รหัสผ่านสำหรับที่เก็บข้อมูลเทียมไม่ควรตรงกับรหัสผ่านที่ใช้กับที่เก็บข้อมูลเทียมจริง', + password_should_not_match: 'รหัสผ่านสำหรับที่เก็บข้อมูลเทียมไม่ควรตรงกับรหัสผ่านที่ใช้กับที่เก็บข้อมูลเทียมจริง', + retype_password: 'ใส่รหัสผ่านอีกครั้ง ใส่รหัสผ่านอีกครั้ง', + passwords_do_not_match: 'รหัสผ่านไม่ตรงกัน ', + success: 'Success', + }, + lnd: { + title: 'จัดการเงิน', + choose_source_wallet: 'เลือกกระเป๋าสตางค์', + refill_lnd_balance: 'เติมกระเป๋าสตางค์ไลท์นิง', + refill: 'เติม', + withdraw: 'ถอน', + expired: 'หมดอายุแล้ว', + sameWalletAsInvoiceError: 'คุณไม่สามารถจ่ายใบแจ้งหนี้นี้ด้วยกระเป๋าสตางค์อันเดียวกันกับที่ใช้สร้างมัน.', + }, +}; diff --git a/loc/ua.js b/loc/ua.js index 560205acc..b5c6d51e2 100644 --- a/loc/ua.js +++ b/loc/ua.js @@ -211,5 +211,7 @@ module.exports = { refill_lnd_balance: 'Збільшити баланс Lightning гаманця', refill: 'Поповнити', withdraw: 'Вивести', + expired: 'Expired', + sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', }, }; diff --git a/models/fiatUnit.js b/models/fiatUnit.js index 184addff2..780a00ef7 100644 --- a/models/fiatUnit.js +++ b/models/fiatUnit.js @@ -1,4 +1,14 @@ export const FiatUnit = Object.freeze({ - USD: { endPointKey: 'btcusd', storageKey: 'BTC_USD', formatterValue: 'USD', symbol: '$' }, - EUR: { endPointKey: 'btceur', storageKey: 'BTC_EUR', formatterValue: 'EUR', symbol: '€' }, + USD: { endPointKey: 'USD', symbol: '$', locale: 'en-US' }, + AUD: { endPointKey: 'AUD', symbol: '$', locale: 'en-AU' }, + EUR: { endPointKey: 'EUR', symbol: '€', locale: 'en-EN' }, + GBP: { endPointKey: 'GBP', symbol: '£', locale: 'en-GB' }, + RUB: { endPointKey: 'RUB', symbol: '₽', locale: 'ru-RU' }, + CAD: { endPointKey: 'CAD', symbol: '$', locale: 'en-CA' }, + CNY: { endPointKey: 'CNY', symbol: '¥', locale: 'zh-CN' }, + JPY: { endPointKey: 'JPY', symbol: '¥', locale: 'ja-JP' }, + INR: { endPointKey: 'INR', symbol: '₹', locale: 'hi-HN' }, + VEF: { endPointKey: 'VEF', symbol: 'Bs.', locale: 'es-VE' }, + SGD: { endPointKey: 'SGD', symbol: 'S$', locale: 'zh-SG' }, + ZAR: { endPointKey: 'ZAR', symbol: 'R', locale: 'en-ZA' }, }); diff --git a/package.json b/package.json index 036d78b8c..51c57c402 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "BlueWallet", - "version": "3.4.0", + "version": "3.5.5", "devDependencies": { "babel-eslint": "^8.2.6", "babel-jest": "23.6.0", diff --git a/prompt.js b/prompt.js index a09338f7a..366962bb7 100644 --- a/prompt.js +++ b/prompt.js @@ -1,6 +1,6 @@ import prompt from 'react-native-prompt-android'; -module.exports = (title, text, isCancelable = true) => { +module.exports = (title, text, isCancelable = true, type = 'secure-text') => { return new Promise((resolve, reject) => { const buttons = isCancelable ? [ @@ -30,7 +30,7 @@ module.exports = (title, text, isCancelable = true) => { ]; prompt(title, text, buttons, { - type: 'secure-text', + type: type, cancelable: isCancelable, }); }); diff --git a/screen/lnd/lndCreateInvoice.js b/screen/lnd/lndCreateInvoice.js new file mode 100644 index 000000000..afafa0948 --- /dev/null +++ b/screen/lnd/lndCreateInvoice.js @@ -0,0 +1,126 @@ +/* global alert */ +import React, { Component } from 'react'; +import { ActivityIndicator, View, TextInput, KeyboardAvoidingView, Keyboard, TouchableWithoutFeedback, Text } from 'react-native'; +import { BlueNavigationStyle, BlueButton, BlueBitcoinAmount } from '../../BlueComponents'; +import PropTypes from 'prop-types'; +import { BitcoinUnit } from '../../models/bitcoinUnits'; +import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; +let EV = require('../../events'); +let loc = require('../../loc'); + +export default class LNDCreateInvoice extends Component { + static navigationOptions = ({ navigation }) => ({ + ...BlueNavigationStyle(navigation, true), + title: loc.receive.header, + }); + + constructor(props) { + super(props); + // fallback to first wallet if it exists + + const fromWallet = props.navigation.getParam('fromWallet'); + this.state = { + fromWallet, + description: '', + isLoading: false, + }; + } + + async createInvoice() { + this.setState({ isLoading: true }, async () => { + try { + const invoiceRequest = await this.state.fromWallet.addInvoice(this.state.amount, this.state.description); + EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); + ReactNativeHapticFeedback.trigger('notificationSuccess', false); + this.props.navigation.navigate('LNDViewInvoice', { + invoice: invoiceRequest, + fromWallet: this.state.fromWallet, + }); + } catch (_error) { + ReactNativeHapticFeedback.trigger('notificationError', false); + this.setState({ isLoading: false }); + alert('Error'); + } + }); + } + + renderCreateButton = () => { + return ( + + {this.state.isLoading ? ( + + ) : ( + 0 && this.state.amount > 0)} + onPress={() => this.createInvoice()} + title={loc.send.details.create} + /> + )} + + ); + }; + + render() { + if (!this.state.fromWallet) { + return ( + + System error: Source wallet not found (this should never happen) + + ); + } + + return ( + + + + + { + this.setState({ amount: text }); + }} + disabled={this.state.isLoading} + unit={BitcoinUnit.SATS} + /> + + this.setState({ description: text })} + placeholder={loc.receive.details.label} + value={this.state.description} + numberOfLines={1} + style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} + editable={!this.state.isLoading} + /> + + {this.renderCreateButton()} + + + + + ); + } +} + +LNDCreateInvoice.propTypes = { + navigation: PropTypes.shape({ + goBack: PropTypes.function, + navigate: PropTypes.func, + getParam: PropTypes.func, + }), +}; diff --git a/screen/lnd/lndViewAdditionalInvoiceInformation.js b/screen/lnd/lndViewAdditionalInvoiceInformation.js new file mode 100644 index 000000000..e96c41054 --- /dev/null +++ b/screen/lnd/lndViewAdditionalInvoiceInformation.js @@ -0,0 +1,101 @@ +/* global alert */ +import React, { Component } from 'react'; +import { Animated, StyleSheet, View, TouchableOpacity, Clipboard, Share } from 'react-native'; +import { BlueLoading, SafeBlueArea, BlueButton, BlueNavigationStyle, BlueText, BlueSpacing20 } from '../../BlueComponents'; +import PropTypes from 'prop-types'; +import { QRCode } from 'react-native-custom-qr-codes'; +/** @type {AppStorage} */ +let BlueApp = require('../../BlueApp'); +const loc = require('../../loc'); + +export default class LNDViewAdditionalInvoiceInformation extends Component { + static navigationOptions = ({ navigation }) => ({ + ...BlueNavigationStyle(navigation, true, () => navigation.dismiss()), + title: 'Additional Information', + }); + + state = { walletInfo: undefined }; + + copyToClipboard = () => { + this.setState({ addressText: loc.receive.details.copiedToClipboard }, () => { + Clipboard.setString(this.state.walletInfo.uris[0]); + setTimeout(() => this.setState({ addressText: this.state.walletInfo.uris[0] }), 1000); + }); + }; + + async componentDidMount() { + const fromWallet = this.props.navigation.getParam('fromWallet'); + try { + await fromWallet.fetchInfo(); + } catch (_) { + alert('Network error'); + return; + } + this.setState({ walletInfo: fromWallet.info_raw, addressText: fromWallet.info_raw.uris[0] }); + } + + render() { + if (typeof this.state.walletInfo === 'undefined') { + return ( + + + + ); + } + + return ( + + + + + + Open direct channel with this node: + + + {this.state.addressText} + + + + + { + Share.share({ + message: this.state.walletInfo.uris[0], + }); + }} + title={loc.receive.details.share} + /> + + + + ); + } +} + +const styles = StyleSheet.create({ + address: { + marginVertical: 32, + fontSize: 15, + color: '#9aa0aa', + textAlign: 'center', + }, +}); + +LNDViewAdditionalInvoiceInformation.propTypes = { + navigation: PropTypes.shape({ + goBack: PropTypes.function, + getParam: PropTypes.function, + dismiss: PropTypes.function, + }), +}; diff --git a/screen/lnd/lndViewInvoice.js b/screen/lnd/lndViewInvoice.js new file mode 100644 index 000000000..763120501 --- /dev/null +++ b/screen/lnd/lndViewInvoice.js @@ -0,0 +1,245 @@ +/* global alert */ +import React, { Component } from 'react'; +import { Animated, StyleSheet, View, TouchableOpacity, Clipboard, Dimensions, Share } from 'react-native'; +import { BlueLoading, BlueText, SafeBlueArea, BlueButton, BlueNavigationStyle, BlueSpacing20 } from '../../BlueComponents'; +import PropTypes from 'prop-types'; +import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; +import { Icon } from 'react-native-elements'; +/** @type {AppStorage} */ +let BlueApp = require('../../BlueApp'); +const loc = require('../../loc'); +const EV = require('../../events'); +const QRFast = require('react-native-qrcode'); +const { width, height } = Dimensions.get('window'); + +export default class LNDViewInvoice extends Component { + static navigationOptions = ({ navigation }) => ({ + ...BlueNavigationStyle(navigation, true, () => navigation.dismiss()), + title: 'Lightning Invoice', + headerLeft: null, + }); + + constructor(props) { + super(props); + const invoice = props.navigation.getParam('invoice'); + const fromWallet = props.navigation.getParam('fromWallet'); + this.state = { + invoice, + fromWallet, + isLoading: typeof invoice === 'string', + addressText: typeof invoice === 'object' && invoice.hasOwnProperty('payment_request') ? invoice.payment_request : invoice, + isFetchingInvoices: true, + qrCodeHeight: height > width ? height / 2.5 : width / 2, + }; + this.fetchInvoiceInterval = undefined; + } + + async componentDidMount() { + this.fetchInvoiceInterval = setInterval(async () => { + if (this.state.isFetchingInvoices) { + try { + const userInvoices = JSON.stringify(await this.state.fromWallet.getUserInvoices()); + const updatedUserInvoice = JSON.parse(userInvoices).filter(invoice => + typeof this.state.invoice === 'object' + ? invoice.payment_request === this.state.invoice.payment_request + : invoice.payment_request === this.state.invoice, + )[0]; + + if (typeof updatedUserInvoice !== 'undefined') { + this.setState({ invoice: updatedUserInvoice, isLoading: false, addressText: updatedUserInvoice.payment_request }); + if (updatedUserInvoice.ispaid) { + // we fetched the invoice, and it is paid :-) + this.setState({ isFetchingInvoices: false }); + ReactNativeHapticFeedback.trigger('notificationSuccess', false); + clearInterval(this.fetchInvoiceInterval); + this.fetchInvoiceInterval = undefined; + EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); + } else { + const currentDate = new Date(); + const now = (currentDate.getTime() / 1000) | 0; + const invoiceExpiration = updatedUserInvoice.timestamp + updatedUserInvoice.expire_time; + if (invoiceExpiration < now && !updatedUserInvoice.ispaid) { + // invoice expired :-( + this.setState({ isFetchingInvoices: false }); + ReactNativeHapticFeedback.trigger('notificationError', false); + clearInterval(this.fetchInvoiceInterval); + this.fetchInvoiceInterval = undefined; + EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); + } + } + } + } catch (error) { + clearInterval(this.fetchInvoiceInterval); + this.fetchInvoiceInterval = undefined; + console.log(error); + alert(error); + this.props.navigation.dismiss(); + } + } + }, 3000); + } + + componentWillUnmount() { + clearInterval(this.fetchInvoiceInterval); + this.fetchInvoiceInterval = undefined; + } + + copyToClipboard = () => { + this.setState({ addressText: loc.receive.details.copiedToClipboard }, () => { + Clipboard.setString(this.state.invoice.payment_request); + setTimeout(() => this.setState({ addressText: this.state.invoice.payment_request }), 1000); + }); + }; + + onLayout = () => { + const { height } = Dimensions.get('window'); + this.setState({ qrCodeHeight: height > width ? height / 2.5 : width / 2 }); + }; + + render() { + if (this.state.isLoading) { + return ; + } + + const { invoice } = this.state; + if (typeof invoice === 'object') { + const currentDate = new Date(); + const now = (currentDate.getTime() / 1000) | 0; + const invoiceExpiration = invoice.timestamp + invoice.expire_time; + + if (invoice.ispaid || invoice.type === 'paid_invoice') { + return ( + + + + + + This invoice has been paid for + + + ); + } + if (invoiceExpiration < now && !invoice.ispaid) { + return ( + + + + + + This invoice was not paid for and has expired + + + ); + } else if (invoiceExpiration > now && invoice.ispaid) { + if (invoice.ispaid) { + return ( + + + 'This invoice has been paid for.' + + + ); + } + } + } + + // Invoice has not expired, nor has it been paid for. + return ( + + + + + + + + {invoice && invoice.amt && Please pay {invoice.amt} sats} + {invoice && invoice.description && For: {invoice.description}} + + + {this.state.addressText} + + + + { + Share.share({ + message: 'lightning:' + invoice.payment_request, + }); + }} + title={loc.receive.details.share} + /> + this.props.navigation.navigate('LNDViewAdditionalInvoiceInformation', { fromWallet: this.state.fromWallet })} + title="Additional Information" + /> + + + + ); + } +} + +const styles = StyleSheet.create({ + address: { + marginVertical: 32, + fontSize: 15, + color: '#9aa0aa', + textAlign: 'center', + }, +}); + +LNDViewInvoice.propTypes = { + navigation: PropTypes.shape({ + goBack: PropTypes.function, + navigate: PropTypes.function, + getParam: PropTypes.function, + dismiss: PropTypes.function, + }), +}; diff --git a/screen/lnd/manageFunds.js b/screen/lnd/manageFunds.js index a145dbe93..c85c6f967 100644 --- a/screen/lnd/manageFunds.js +++ b/screen/lnd/manageFunds.js @@ -1,138 +1,77 @@ /* global alert */ import React, { Component } from 'react'; -import { TouchableOpacity, View } from 'react-native'; -import { Dropdown } from 'react-native-material-dropdown'; -import { BlueSpacingVariable, BlueLoading, SafeBlueArea, BlueCard, BlueHeaderDefaultSub } from '../../BlueComponents'; +import { TouchableOpacity, Linking, View } from 'react-native'; +import { BlueSpacingVariable, BlueNavigationStyle, SafeBlueArea, BlueCard } from '../../BlueComponents'; import { ListItem } from 'react-native-elements'; import PropTypes from 'prop-types'; -import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; /** @type {AppStorage} */ let BlueApp = require('../../BlueApp'); let loc = require('../../loc'); -let data = []; - export default class ManageFunds extends Component { - static navigationOptions = { - header: ({ navigation }) => { - return navigation.goBack(null)} />; - }, - }; + static navigationOptions = ({ navigation }) => ({ + ...BlueNavigationStyle(navigation, true), + title: loc.lnd.title, + headerLeft: null, + }); constructor(props) { super(props); - let fromSecret; - if (props.navigation.state.params.fromSecret) fromSecret = props.navigation.state.params.fromSecret; - let fromWallet = false; + this.onWalletSelect = this.onWalletSelect.bind(this); - for (let w of BlueApp.getWallets()) { - if (w.getSecret() === fromSecret) { - fromWallet = w; - break; - } - } - - if (fromWallet) { - console.log(fromWallet.type); - } - - this.state = { - fromWallet, - fromSecret, - isLoading: true, - }; + this.state = { fromWallet: props.navigation.getParam('fromWallet') }; } - async componentDidMount() { - data = []; - for (let c = 0; c < BlueApp.getWallets().length; c++) { - let w = BlueApp.getWallets()[c]; - if (w.type !== LightningCustodianWallet.type) { - data.push({ - value: c, - label: w.getLabel() + ' (' + w.getBalance() + ' BTC)', - }); + async onWalletSelect(wallet) { + this.props.navigation.dismiss(); + /** @type {LightningCustodianWallet} */ + let toAddress = false; + if (this.state.fromWallet.refill_addressess.length > 0) { + toAddress = this.state.fromWallet.refill_addressess[0]; + } else { + try { + await this.state.fromWallet.fetchBtcAddress(); + toAddress = this.state.fromWallet.refill_addressess[0]; + } catch (Err) { + return alert(Err.message); } } - this.setState({ - isLoading: false, - }); + if (wallet) { + setTimeout(() => { + this.props.navigation.navigate('SendDetails', { + memo: loc.lnd.refill_lnd_balance, + fromSecret: wallet.getSecret(), + address: toAddress, + }); + }, 100); + } else { + return alert('Internal error'); + } } render() { - if (this.state.isLoading) { - return ; - } - return ( - {(() => { - if (this.state.isRefill) { - return ( - - { - /** @type {LightningCustodianWallet} */ - let fromWallet = this.state.fromWallet; - let toAddress = false; - if (fromWallet.refill_addressess.length > 0) { - toAddress = fromWallet.refill_addressess[0]; - } else { - try { - await fromWallet.fetchBtcAddress(); - toAddress = fromWallet.refill_addressess[0]; - } catch (Err) { - return alert(Err.message); - } - } - - let wallet = BlueApp.getWallets()[value]; - if (wallet) { - console.log(wallet.getSecret()); - setTimeout(() => { - console.log({ toAddress }); - this.props.navigation.navigate('SendDetails', { - memo: loc.lnd.refill_lnd_balance, - fromSecret: wallet.getSecret(), - address: toAddress, - }); - }, 750); - } else { - return alert('Internal error'); - } - }} - /> - - ); - } else { - return ( - - { - this.setState({ isRefill: true }); - }} - title={loc.lnd.refill} - /> - { - alert('Coming soon'); - }} - title={loc.lnd.withdraw} - /> - - ); - } - })()} + { + this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect }); + }} + title={loc.lnd.refill} + /> + { + Linking.openURL('https://zigzag.io'); + }} + title={loc.lnd.withdraw} + /> @@ -144,7 +83,9 @@ export default class ManageFunds extends Component { ManageFunds.propTypes = { navigation: PropTypes.shape({ goBack: PropTypes.function, + dismiss: PropTypes.function, navigate: PropTypes.function, + getParam: PropTypes.function, state: PropTypes.shape({ params: PropTypes.shape({ fromSecret: PropTypes.string, diff --git a/screen/lnd/scanLndInvoice.js b/screen/lnd/scanLndInvoice.js index 5845f81a8..ea931b460 100644 --- a/screen/lnd/scanLndInvoice.js +++ b/screen/lnd/scanLndInvoice.js @@ -3,50 +3,58 @@ import React from 'react'; import { Text, Dimensions, ActivityIndicator, View, TouchableOpacity, TouchableWithoutFeedback, TextInput, Keyboard } from 'react-native'; import { Icon } from 'react-native-elements'; import PropTypes from 'prop-types'; -import { BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueHeaderDefaultSub } from '../../BlueComponents'; +import { BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueNavigationStyle, BlueBitcoinAmount } from '../../BlueComponents'; import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; +import { BitcoinUnit } from '../../models/bitcoinUnits'; /** @type {AppStorage} */ let BlueApp = require('../../BlueApp'); -let currency = require('../../currency'); let EV = require('../../events'); let loc = require('../../loc'); const { width } = Dimensions.get('window'); export default class ScanLndInvoice extends React.Component { - static navigationOptions = { - header: ({ navigation }) => { - return navigation.goBack(null)} />; - }, - }; + static navigationOptions = ({ navigation }) => ({ + ...BlueNavigationStyle(navigation, true), + title: loc.send.header, + headerLeft: null, + }); state = { isLoading: false, + isAmountInitiallyEmpty: false, }; constructor(props) { super(props); - let fromSecret; - if (props.navigation.state.params.fromSecret) fromSecret = props.navigation.state.params.fromSecret; - let fromWallet = {}; - if (!fromSecret) { - const lightningWallets = BlueApp.getWallets().filter(item => item.type === LightningCustodianWallet.type); - if (lightningWallets.length > 0) { - fromSecret = lightningWallets[0].getSecret(); + if (!BlueApp.getWallets().some(item => item.type === LightningCustodianWallet.type)) { + alert('Before paying a Lightning invoice, you must first add a Lightning wallet.'); + props.navigation.dismiss(); + } else { + let fromSecret; + if (props.navigation.state.params.fromSecret) fromSecret = props.navigation.state.params.fromSecret; + let fromWallet = {}; + + if (!fromSecret) { + const lightningWallets = BlueApp.getWallets().filter(item => item.type === LightningCustodianWallet.type); + if (lightningWallets.length > 0) { + fromSecret = lightningWallets[0].getSecret(); + } } - } - for (let w of BlueApp.getWallets()) { - if (w.getSecret() === fromSecret) { - fromWallet = w; - break; + for (let w of BlueApp.getWallets()) { + if (w.getSecret() === fromSecret) { + fromWallet = w; + break; + } } - } - this.state = { - fromWallet, - fromSecret, - }; + this.state = { + fromWallet, + fromSecret, + destination: '', + }; + } } async componentDidMount() { @@ -71,7 +79,7 @@ export default class ScanLndInvoice extends React.Component { }, 6000); if (!this.state.fromWallet) { - alert('Error: cant find source wallet (this should never happen)'); + alert('Before paying a Lightning invoice, you must first add a Lightning wallet.'); return this.props.navigation.goBack(); } @@ -82,7 +90,7 @@ export default class ScanLndInvoice extends React.Component { * @type {LightningCustodianWallet} */ let w = this.state.fromWallet; - let decoded = false; + let decoded; try { decoded = await w.decodeInvoice(data); @@ -94,10 +102,11 @@ export default class ScanLndInvoice extends React.Component { } Keyboard.dismiss(); this.setState({ - isPaying: true, invoice: data, decoded, expiresIn, + destination: data, + isAmountInitiallyEmpty: decoded.num_satoshis === '0', }); } catch (Err) { alert(Err.message); @@ -108,62 +117,89 @@ export default class ScanLndInvoice extends React.Component { if (!this.state.hasOwnProperty('decoded')) { return null; } - let decoded = this.state.decoded; - /** @type {LightningCustodianWallet} */ - let fromWallet = this.state.fromWallet; + this.setState( + { + isLoading: true, + }, + async () => { + let decoded = this.state.decoded; - let expiresIn = (decoded.timestamp * 1 + decoded.expiry * 1) * 1000; // ms - if (+new Date() > expiresIn) { - return alert('Invoice expired'); - } + /** @type {LightningCustodianWallet} */ + let fromWallet = this.state.fromWallet; - this.setState({ - isPayingInProgress: true, - }); + let expiresIn = (decoded.timestamp * 1 + decoded.expiry * 1) * 1000; // ms + if (+new Date() > expiresIn) { + this.setState({ isLoading: false }); + return alert('Invoice expired'); + } - let start = +new Date(); - let end; - try { - await fromWallet.payInvoice(this.state.invoice); - end = +new Date(); - } catch (Err) { - console.log(Err.message); - this.props.navigation.goBack(); - return alert('Error'); - } + const currentUserInvoices = await fromWallet.getUserInvoices(); + if (currentUserInvoices.some(invoice => invoice.payment_hash === decoded.payment_hash)) { + this.setState({ isLoading: false }); + return alert(loc.lnd.sameWalletAsInvoiceError); + } - console.log('payInvoice took', (end - start) / 1000, 'sec'); - EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs + let start = +new Date(); + let end; + try { + await fromWallet.payInvoice(this.state.invoice, this.state.invoice.num_satoshis); + end = +new Date(); + } catch (Err) { + console.log(Err.message); + this.setState({ isLoading: false }); + this.props.navigation.goBack(); + return alert('Error'); + } - alert('Success'); - this.props.navigation.goBack(); + console.log('payInvoice took', (end - start) / 1000, 'sec'); + EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED); // someone should fetch txs + + alert('Success'); + this.props.navigation.goBack(); + }, + ); } processTextForInvoice = text => { if (text.toLowerCase().startsWith('lnb') || text.toLowerCase().startsWith('lightning:lnb')) { this.processInvoice(text); } else { - this.setState({ decoded: undefined, expiresIn: undefined }); + this.setState({ decoded: undefined, expiresIn: undefined, destination: text }); } }; + shouldDisablePayButton = () => { + if (typeof this.state.decoded !== 'object') { + return true; + } else { + if (!this.state.decoded.hasOwnProperty('num_satoshis')) { + return true; + } + } + return this.state.decoded.num_satoshis <= 0 || this.state.isLoading || isNaN(this.state.decoded.num_satoshis); + }; + render() { return ( - - {this.state.hasOwnProperty('decoded') && - this.state.decoded !== undefined && - currency.satoshiToLocalCurrency(this.state.decoded.num_satoshis)} - - - {this.state.hasOwnProperty('decoded') && - this.state.decoded !== undefined && - currency.satoshiToBTC(this.state.decoded.num_satoshis)} - + { + if (typeof this.state.decoded === 'object') { + text = parseInt(text); + let decoded = this.state.decoded; + decoded.num_satoshis = text; + this.setState({ decoded: decoded }); + } + }} + disabled={typeof this.state.decoded !== 'object' || this.state.isLoading} + unit={BitcoinUnit.SATS} + /> - { + this.setState({ destination: text }); + this.processTextForInvoice(text); + }} placeholder={loc.wallets.details.destination} numberOfLines={1} - value={this.state.hasOwnProperty('decoded') && this.state.decoded !== undefined ? this.state.decoded.destination : ''} + value={this.state.destination} style={{ flex: 1, marginHorizontal: 8, minHeight: 33, height: 33 }} editable={!this.state.isLoading} /> @@ -212,61 +251,40 @@ export default class ScanLndInvoice extends React.Component { - {}} - placeholder={loc.wallets.details.description} - numberOfLines={1} - value={this.state.hasOwnProperty('decoded') && this.state.decoded !== undefined ? this.state.decoded.description : ''} - style={{ flex: 1, marginHorizontal: 8, minHeight: 33, height: 33 }} - editable={!this.state.isLoading} - /> + + {this.state.hasOwnProperty('decoded') && this.state.decoded !== undefined ? this.state.decoded.description : ''} + {this.state.expiresIn !== undefined && ( Expires in: {this.state.expiresIn} )} - - - {this.state.hasOwnProperty('decoded') && - this.state.decoded !== undefined && - (() => { - if (this.state.isPayingInProgress) { - return ( - - - - ); - } else { - return ( - { - this.pay(); - }} - /> - ); - } - })()} + {this.state.isLoading ? ( + + + + ) : ( + { + this.pay(); + }} + disabled={this.shouldDisablePayButton()} + /> + )} ); @@ -278,6 +296,7 @@ ScanLndInvoice.propTypes = { goBack: PropTypes.function, navigate: PropTypes.function, getParam: PropTypes.function, + dismiss: PropTypes.function, state: PropTypes.shape({ params: PropTypes.shape({ uri: PropTypes.string, diff --git a/screen/plausibledeniability.js b/screen/plausibledeniability.js index edfebe070..cf5325d4d 100644 --- a/screen/plausibledeniability.js +++ b/screen/plausibledeniability.js @@ -1,8 +1,7 @@ /* global alert */ import React, { Component } from 'react'; import { ScrollView } from 'react-native'; -import Ionicons from 'react-native-vector-icons/Ionicons'; -import { BlueLoading, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueHeader, BlueSpacing20 } from '../BlueComponents'; +import { BlueLoading, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle, BlueSpacing20 } from '../BlueComponents'; import PropTypes from 'prop-types'; /** @type {AppStorage} */ let BlueApp = require('../BlueApp'); @@ -12,10 +11,8 @@ let loc = require('../loc'); export default class PlausibleDeniability extends Component { static navigationOptions = { - tabBarLabel: loc.plausibledeniability.title, - tabBarIcon: ({ tintColor, focused }) => ( - - ), + ...BlueNavigationStyle(), + title: loc.plausibledeniability.title, }; constructor(props) { @@ -38,14 +35,6 @@ export default class PlausibleDeniability extends Component { return ( - - {loc.plausibledeniability.help} @@ -85,20 +74,6 @@ export default class PlausibleDeniability extends Component { this.props.navigation.navigate('Wallets'); }} /> - - - - { - this.props.navigation.goBack(); - }} - /> diff --git a/screen/send/details.js b/screen/send/details.js index db77b8e95..2f2b341c7 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -146,9 +146,15 @@ export default class SendDetails extends Component { let memo = ''; parsedBitcoinUri = bip21.decode(this.props.navigation.state.params.uri); - address = parsedBitcoinUri.address || address; - amount = parsedBitcoinUri.options.amount.toString() || amount; - memo = parsedBitcoinUri.options.label || memo; + address = parsedBitcoinUri.hasOwnProperty('address') ? parsedBitcoinUri.address : address; + if (parsedBitcoinUri.hasOwnProperty('options')) { + if (parsedBitcoinUri.options.hasOwnProperty('amount')) { + amount = parsedBitcoinUri.options.amount.toString(); + } + if (parsedBitcoinUri.options.hasOwnProperty('label')) { + memo = parsedBitcoinUri.options.label || memo; + } + } this.setState({ address, amount, memo }); } catch (error) { console.log(error); @@ -223,7 +229,7 @@ export default class SendDetails extends Component { }) .catch(error => { alert(error.errorMessage); - this.setState({ address: text.replace(' ', ''), isLoading: false, bip70TransactionExpiration: null, amount: 0 }); + this.setState({ isLoading: false, bip70TransactionExpiration: null }); }); }, ); diff --git a/screen/settings/about.js b/screen/settings/about.js index 382784539..7d59f994b 100644 --- a/screen/settings/about.js +++ b/screen/settings/about.js @@ -50,64 +50,65 @@ export default class About extends Component { Always backup your keys + - { - Linking.openURL('https://github.com/BlueWallet/BlueWallet'); - }} - title="github.com/BlueWallet/BlueWallet" - /> - + { + Linking.openURL('https://github.com/BlueWallet/BlueWallet'); + }} + title="github.com/BlueWallet/BlueWallet" + /> + - { - Linking.openURL('https://twitter.com/bluewalletio'); - }} - title="Follow us on Twitter" - /> - + { + Linking.openURL('https://twitter.com/bluewalletio'); + }} + title="Follow us on Twitter" + /> + - { - Linking.openURL('https://t.me/bluewallet'); - }} - title="Join Telegram chat" - /> - + { + Linking.openURL('https://t.me/bluewallet'); + }} + title="Join Telegram chat" + /> + - { - if (Platform.OS === 'ios') { - Linking.openURL('https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8'); - } else { - Linking.openURL('https://play.google.com/store/apps/details?id=io.bluewallet.bluewallet'); - } - }} - title="Leave us a review on Appstore" - /> - - + { + if (Platform.OS === 'ios') { + Linking.openURL('https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8'); + } else { + Linking.openURL('https://play.google.com/store/apps/details?id=io.bluewallet.bluewallet'); + } + }} + title="Leave us a review on Appstore" + /> + + Built with awesome: * React Native diff --git a/screen/settings/currency.js b/screen/settings/currency.js index 40b62f9f3..45da20b90 100644 --- a/screen/settings/currency.js +++ b/screen/settings/currency.js @@ -1,11 +1,9 @@ import React, { Component } from 'react'; -import { FlatList, TouchableOpacity, AsyncStorage, ActivityIndicator, View } from 'react-native'; -import { SafeBlueArea, BlueNavigationStyle, BlueListItem } from '../../BlueComponents'; +import { FlatList, TouchableOpacity, ActivityIndicator, View } from 'react-native'; +import { SafeBlueArea, BlueNavigationStyle, BlueListItem, BlueText, BlueCard } from '../../BlueComponents'; import PropTypes from 'prop-types'; import { Icon } from 'react-native-elements'; -import { AppStorage } from '../../class'; import { FiatUnit } from '../../models/fiatUnit'; -/** @type {AppStorage} */ let loc = require('../../loc'); let currency = require('../../currency'); @@ -22,11 +20,11 @@ export default class Currency extends Component { async componentDidMount() { try { - const preferredCurrency = await AsyncStorage.getItem(AppStorage.PREFERREDCURRENCY); + const preferredCurrency = await currency.getPreferredCurrency(); if (preferredCurrency === null) { throw Error(); } - this.setState({ selectedCurrency: JSON.parse(preferredCurrency) }); + this.setState({ selectedCurrency: preferredCurrency }); } catch (_error) { this.setState({ selectedCurrency: FiatUnit.USD }); } @@ -37,15 +35,15 @@ export default class Currency extends Component { { this.setState({ isSavingNewPreferredCurrency: true, selectedCurrency: item }, async () => { - await AsyncStorage.setItem(AppStorage.PREFERREDCURRENCY, JSON.stringify(item)); - await currency.startUpdater(true); + await currency.setPrefferedCurrency(item); + await currency.startUpdater(); this.setState({ isSavingNewPreferredCurrency: false }); }); }} > @@ -70,6 +68,9 @@ export default class Currency extends Component { extraData={this.state.data} renderItem={this.renderItem} /> + + Prices are obtained from CoinDesk + ); } diff --git a/screen/settings/language.js b/screen/settings/language.js index bc945dc10..0330b0003 100644 --- a/screen/settings/language.js +++ b/screen/settings/language.js @@ -1,9 +1,8 @@ import React, { Component } from 'react'; -import { Picker } from 'react-native'; -import { BlueLoading, SafeBlueArea, BlueCard, BlueNavigationStyle } from '../../BlueComponents'; +import { FlatList, TouchableOpacity } from 'react-native'; +import { BlueLoading, BlueText, SafeBlueArea, BlueListItem, BlueCard, BlueNavigationStyle } from '../../BlueComponents'; import PropTypes from 'prop-types'; -/** @type {AppStorage} */ -let BlueApp = require('../../BlueApp'); +import { Icon } from 'react-native-elements'; let loc = require('../../loc'); export default class Language extends Component { @@ -17,16 +16,50 @@ export default class Language extends Component { this.state = { isLoading: true, language: loc.getLanguage(), + availableLanguages: [ + { label: 'English', value: 'en' }, + { label: 'Русский', value: 'ru' }, + { label: 'Українська', value: 'ua' }, + { label: 'Spanish', value: 'es' }, + { label: 'Portuguese (BR)', value: 'pt_br' }, + { label: 'Portuguese (PT)', value: 'pt_pt' }, + { label: 'Deutsch (DE)', value: 'de_de' }, + { label: 'Česky (CZ)', value: 'cs_cz' }, + { label: 'Thai (TH)', value: 'th_th' }, + { label: 'Dutch (NL)', value: 'nl_nl' }, + { label: 'Français (FR)', value: 'fr_fr' }, + ], }; } async componentDidMount() { this.setState({ isLoading: false, - storageIsEncrypted: await BlueApp.storageIsEncrypted(), }); } + renderItem = ({ item }) => { + return ( + { + console.log('setLanguage', item.value); + loc.setLanguage(item.value); + loc.saveLanguage(item.value); + return this.setState({ language: item.value }); + }} + > + , + } + : { hideChevron: true })} + /> + + ); + }; + render() { if (this.state.isLoading) { return ; @@ -34,24 +67,15 @@ export default class Language extends Component { return ( + `${index}`} + data={this.state.availableLanguages} + extraData={this.state.availableLanguages} + renderItem={this.renderItem} + /> - { - console.log('setLanguage', itemValue); - loc.setLanguage(itemValue); - loc.saveLanguage(itemValue); - return this.setState({ language: itemValue }); - }} - > - - - - - - - - + When selecting a new language, restarting Blue Wallet may be required for the change to take effect. ); diff --git a/screen/settings/lightningSettings.js b/screen/settings/lightningSettings.js index 802747ef7..1d4c0add7 100644 --- a/screen/settings/lightningSettings.js +++ b/screen/settings/lightningSettings.js @@ -1,16 +1,7 @@ import React, { Component } from 'react'; import { AsyncStorage, View, TextInput, Linking } from 'react-native'; import { AppStorage } from '../../class'; -import { - BlueLoading, - BlueSpacing20, - BlueButton, - SafeBlueArea, - BlueCard, - BlueNavigationStyle, - BlueSpacing40, - BlueText, -} from '../../BlueComponents'; +import { BlueLoading, BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueNavigationStyle, BlueText } from '../../BlueComponents'; import PropTypes from 'prop-types'; import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; /** @type {AppStorage} */ @@ -65,25 +56,25 @@ export default class LightningSettings extends Component { To connect to your own LND node please install LndHub and put its URL here in settings. Leave blank to use default LndHub (lndhub.io) + - { - Linking.openURL('https://github.com/BlueWallet/LndHub'); - }} - title="github.com/BlueWallet/LndHub" - buttonStyle={{ - backgroundColor: '#FFFFFF', - }} - /> - - + { + Linking.openURL('https://github.com/BlueWallet/LndHub'); + }} + title="github.com/BlueWallet/LndHub" + buttonStyle={{ + backgroundColor: '#FFFFFF', + }} + /> + - { - this.props.navigation.goBack(); - setTimeout(async () => { - let w; + {!this.state.isLoading ? ( + { + this.setState( + { isLoading: true }, + async () => { + let w; - if (this.state.activeLightning) { - // lightning was selected + if (this.state.activeLightning) { + // lightning was selected + // eslint-disable-next-line +for (let t of BlueApp.getWallets()) { + if (t.type === LightningCustodianWallet.type) { + // already exist + this.setState({ isLoading: false }); + return alert('Only 1 Lightning wallet allowed for now'); + } + } - global.lightning_create_try = global.lightning_create_try || 1; - if (global.lightning_create_try++ < 9 && +new Date() < 1545264000000) return alert('Coming soon'); - // eslint-disable-next-line - for (let t of BlueApp.getWallets()) { - if (t.type === LightningCustodianWallet.type) { - // already exist - return alert('Only 1 Ligthning wallet allowed for now'); + w = new LightningCustodianWallet(); + w.setLabel(this.state.label || w.typeReadable); + + try { + let lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); + if (lndhub) { + w.setBaseURI(lndhub); + w.init(); + } + await w.createAccount(); + await w.authorize(); + } catch (Err) { + this.setState({ isLoading: false }); + console.warn('lnd create failure', Err); + // giving app, not adding anything + } + A(A.ENUM.CREATED_LIGHTNING_WALLET); + } else if (this.state.selectedIndex === 1) { + // btc was selected + // index 1 radio - segwit single address + w = new SegwitP2SHWallet(); + w.setLabel(this.state.label || loc.wallets.add.label_new_segwit); + } else { + // zero index radio - HD segwit + w = new HDSegwitP2SHWallet(); + w.setLabel((this.state.label || loc.wallets.add.label_new_segwit) + ' HD'); } - } - w = new LightningCustodianWallet(); - w.setLabel(this.state.label || w.typeReadable); - - try { - let lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); - if (lndhub) { - w.setBaseURI(lndhub); - w.init(); - } - await w.createAccount(); - await w.authorize(); - } catch (Err) { - console.warn('lnd create failure', Err); - // giving app, not adding anything - } - A(A.ENUM.CREATED_LIGHTNING_WALLET); - } else if (this.state.selectedIndex === 1) { - // btc was selected - // index 1 radio - segwit single address - w = new SegwitP2SHWallet(); - w.setLabel(this.state.label || loc.wallets.add.label_new_segwit); - } else { - // zero index radio - HD segwit - w = new HDSegwitP2SHWallet(); - w.setLabel((this.state.label || loc.wallets.add.label_new_segwit) + ' HD'); - } - - await w.generate(); - BlueApp.wallets.push(w); - await BlueApp.saveToDisk(); - EV(EV.enum.WALLETS_COUNT_CHANGED); - A(A.ENUM.CREATED_WALLET); - ReactNativeHapticFeedback.trigger('notificationSuccess', false); - }, 1); - }} - /> + await w.generate(); + BlueApp.wallets.push(w); + await BlueApp.saveToDisk(); + EV(EV.enum.WALLETS_COUNT_CHANGED); + A(A.ENUM.CREATED_WALLET); + ReactNativeHapticFeedback.trigger('notificationSuccess', false); + this.props.navigation.dismiss(); + }, + 1, + ); + }} + /> + ) : ( + + )} { - Linking.openURL( - 'https://payments.changelly.com/?crypto=USD&fiat=USD&ref_id=rtagfcvnwiwvhm99&address=' + this.state.address, - ); + Linking.openURL('https://old.changelly.com/?ref_id=rtagfcvnwiwvhm99'); }} title="Buy from Changelly" /> diff --git a/screen/wallets/export.js b/screen/wallets/export.js index 29cfefd61..7e26e20e3 100644 --- a/screen/wallets/export.js +++ b/screen/wallets/export.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { Dimensions, Platform, ActivityIndicator, View } from 'react-native'; import { QRCode as QRSlow } from 'react-native-custom-qr-codes'; -import { BlueSpacing40, SafeBlueArea, BlueCard, BlueText, BlueHeaderDefaultSub } from '../../BlueComponents'; +import { BlueSpacing40, SafeBlueArea, BlueNavigationStyle, BlueCard, BlueText } from '../../BlueComponents'; import PropTypes from 'prop-types'; const QRFast = require('react-native-qrcode'); /** @type {AppStorage} */ @@ -17,11 +17,11 @@ if (aspectRatio > 1.6) { } export default class WalletExport extends Component { - static navigationOptions = { - header: ({ navigation }) => { - return navigation.goBack(null)} />; - }, - }; + static navigationOptions = ({ navigation }) => ({ + ...BlueNavigationStyle(navigation, true), + title: loc.wallets.export.title, + headerLeft: null, + }); constructor(props) { super(props); @@ -29,7 +29,6 @@ export default class WalletExport extends Component { let address = props.navigation.state.params.address; let secret = props.navigation.state.params.secret; let wallet; - for (let w of BlueApp.getWallets()) { if ((address && w.getAddress() === address) || w.getSecret() === secret) { // found our wallet diff --git a/screen/wallets/import.js b/screen/wallets/import.js index 1d35ad4d4..d7e769754 100644 --- a/screen/wallets/import.js +++ b/screen/wallets/import.js @@ -17,7 +17,7 @@ import { BlueButton, SafeBlueArea, BlueSpacing20, - BlueHeaderDefaultSub, + BlueNavigationStyle, } from '../../BlueComponents'; import PropTypes from 'prop-types'; import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; @@ -30,11 +30,10 @@ let loc = require('../../loc'); const { width } = Dimensions.get('window'); export default class WalletsImport extends Component { - static navigationOptions = { - header: ({ navigation }) => { - return navigation.goBack(null)} />; - }, - }; + static navigationOptions = ({ navigation }) => ({ + ...BlueNavigationStyle(), + title: loc.wallets.import.title, + }); constructor(props) { super(props); @@ -43,7 +42,7 @@ export default class WalletsImport extends Component { }; } - async componentDidMount() { + componentDidMount() { this.setState({ isLoading: false, label: '', @@ -51,14 +50,18 @@ export default class WalletsImport extends Component { } async _saveWallet(w) { - alert(loc.wallets.import.success); - ReactNativeHapticFeedback.trigger('notificationSuccess', false); - w.setLabel(loc.wallets.import.imported + ' ' + w.typeReadable); - BlueApp.wallets.push(w); - await BlueApp.saveToDisk(); - EV(EV.enum.WALLETS_COUNT_CHANGED); - A(A.ENUM.CREATED_WALLET); - this.props.navigation.dismiss(); + if (BlueApp.getWallets().some(wallet => wallet.getSecret() === w.secret)) { + alert('This wallet has been previously imported.'); + } else { + alert(loc.wallets.import.success); + ReactNativeHapticFeedback.trigger('notificationSuccess', false); + w.setLabel(loc.wallets.import.imported + ' ' + w.typeReadable); + BlueApp.wallets.push(w); + await BlueApp.saveToDisk(); + EV(EV.enum.WALLETS_COUNT_CHANGED); + A(A.ENUM.CREATED_WALLET); + this.props.navigation.dismiss(); + } } async importMnemonic(text) { @@ -227,6 +230,7 @@ export default class WalletsImport extends Component { }} > { + if (item.type === 'user_invoice' || item.type === 'payment_request') { + if (isNaN(item.value)) { + item.value = "0" + } + const currentDate = new Date(); + const now = (currentDate.getTime() / 1000) | 0; + const invoiceExpiration = item.timestamp + item.expire_time; + + if (invoiceExpiration > now) { + return loc.formatBalanceWithoutSuffix(item.value && item.value, BitcoinUnit.BTC).toString(); + } else if (invoiceExpiration < now) { + if (item.ispaid) { + return loc.formatBalanceWithoutSuffix(item.value && item.value, BitcoinUnit.BTC).toString(); + } else { + return loc.lnd.expired; + } + } + } else { + return loc.formatBalanceWithoutSuffix(item.value && item.value, BitcoinUnit.BTC).toString(); + } + }; + + rowTitleStyle = item => { + let color = '#37c0a1'; + + if (item.type === 'user_invoice' || item.type === 'payment_request') { + const currentDate = new Date(); + const now = (currentDate.getTime() / 1000) | 0; + const invoiceExpiration = item.timestamp + item.expire_time; + + if (invoiceExpiration > now) { + color = '#37c0a1'; + } else if (invoiceExpiration < now) { + if (item.ispaid) { + color = '#37c0a1'; + } else { + color = '#FF0000'; + } + } + } else if (item.value / 100000000 < 0) { + color = BlueApp.settings.foregroundColor; + } + + return { + fontWeight: '600', + fontSize: 16, + color: color, + }; + }; + render() { const { navigate } = this.props.navigation; if (this.state.isLoading) { return ; } - return ( + + + ); + } + } else { + return ( + + + + ); + } + } + if (!rowData.item.confirmations) { return ( @@ -337,6 +417,20 @@ export default class WalletsList extends Component { navigate('TransactionDetails', { hash: rowData.item.hash, }); + } else if ( + rowData.item.type === 'user_invoice' || + rowData.item.type === 'payment_request' || + rowData.item.type === 'paid_invoice' + ) { + const lightningWallet = this.state.wallets.filter(wallet => wallet.type === LightningCustodianWallet.type); + if (typeof lightningWallet === 'object') { + if (lightningWallet.length === 1) { + this.props.navigation.navigate('LNDViewInvoice', { + invoice: rowData.item, + fromWallet: lightningWallet[0], + }); + } + } } }} badge={{ @@ -345,15 +439,8 @@ export default class WalletsList extends Component { containerStyle: { marginTop: 0 }, }} hideChevron - rightTitle={loc.formatBalanceWithoutSuffix(rowData.item.value && rowData.item.value, BitcoinUnit.BTC)} - rightTitleStyle={{ - fontWeight: '600', - fontSize: 16, - color: - rowData.item.value / 100000000 < 0 || rowData.item.type === 'paid_invoice' - ? BlueApp.settings.foregroundColor - : '#37c0a1', - }} + rightTitle={this.rowTitle(rowData.item)} + rightTitleStyle={this.rowTitleStyle(rowData.item)} /> ); }} diff --git a/screen/wallets/selectWallet.js b/screen/wallets/selectWallet.js index 7fd53d713..b5173a6a0 100644 --- a/screen/wallets/selectWallet.js +++ b/screen/wallets/selectWallet.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import { View, ActivityIndicator, Image, Text, TouchableOpacity, FlatList } from 'react-native'; -import { SafeBlueArea, BlueNavigationStyle } from '../../BlueComponents'; +import { SafeBlueArea, BlueNavigationStyle, BlueText, BlueSpacing20 } from '../../BlueComponents'; import LinearGradient from 'react-native-linear-gradient'; import PropTypes from 'prop-types'; import { WatchOnlyWallet, LegacyWallet } from '../../class'; @@ -157,12 +157,24 @@ export default class SelectWallet extends Component { }; render() { - if (this.state.isLoading || this.state.data.length <= 0) { + if (this.state.isLoading) { return ( - + ); + } else if (this.state.data.length <= 0) { + return ( + + + There are currently no Bitcoin wallets available. + + + A Bitcoin wallet is required to refill Lightning wallets. Please, create or import one. + + + + ); } return ( diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index b8bc3bdf6..29bf4e856 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { Text, View, Image, FlatList, RefreshControl, TouchableOpacity } from 'react-native'; +import { Text, View, Image, FlatList, RefreshControl, TouchableOpacity, StatusBar } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import PropTypes from 'prop-types'; import { NavigationEvents } from 'react-navigation'; @@ -7,6 +7,7 @@ import { BlueText, BlueTransactionOnchainIcon, ManageFundsBigButton, + BlueTransactionExpiredIcon, BlueTransactionIncommingIcon, BlueTransactionOutgoingIcon, BlueTransactionPendingIcon, @@ -14,13 +15,13 @@ import { BlueSendButtonIcon, BlueReceiveButtonIcon, BlueListItem, + BlueTransactionOffchainIncomingIcon, } from '../../BlueComponents'; import { Icon } from 'react-native-elements'; import { BitcoinUnit } from '../../models/bitcoinUnits'; import { LightningCustodianWallet } from '../../class'; /** @type {AppStorage} */ let BlueApp = require('../../BlueApp'); - let loc = require('../../loc'); let EV = require('../../events'); @@ -155,6 +156,9 @@ export default class WalletTransactions extends Component { if (wallet.fetchPendingTransactions) { await wallet.fetchPendingTransactions(); } + if (wallet.fetchUserInvoices) { + await wallet.fetchUserInvoices(); + } let end = +new Date(); console.log(wallet.getLabel(), 'fetch tx took', (end - start) / 1000, 'sec'); } catch (err) { @@ -289,15 +293,72 @@ export default class WalletTransactions extends Component { }; async onWillBlur() { + StatusBar.setBarStyle('dark-content'); await BlueApp.saveToDisk(); } + componentWillUnmount() { + this.onWillBlur(); + } + + rowTitle = item => { + if (item.type === 'user_invoice' || item.type === 'payment_request') { + if (isNaN(item.value)) { + item.value = 0 + } + const currentDate = new Date(); + const now = (currentDate.getTime() / 1000) | 0; + const invoiceExpiration = item.timestamp + item.expire_time; + + if (invoiceExpiration > now) { + return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit()).toString(); + } else if (invoiceExpiration < now) { + if (item.ispaid) { + return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit()).toString(); + } else { + return loc.lnd.expired; + } + } + } else { + return loc.formatBalanceWithoutSuffix(item.value && item.value, this.state.wallet.getPreferredBalanceUnit()).toString(); + } + }; + + rowTitleStyle = item => { + let color = '#37c0a1'; + + if (item.type === 'user_invoice' || item.type === 'payment_request') { + const currentDate = new Date(); + const now = (currentDate.getTime() / 1000) | 0; + const invoiceExpiration = item.timestamp + item.expire_time; + + if (invoiceExpiration > now) { + color = '#37c0a1'; + } else if (invoiceExpiration < now) { + if (item.ispaid) { + color = '#37c0a1'; + } else { + color = '#FF0000'; + } + } + } else if (item.value / 100000000 < 0) { + color = BlueApp.settings.foregroundColor; + } + + return { + fontWeight: '600', + fontSize: 16, + color: color, + }; + }; + render() { const { navigate } = this.props.navigation; return ( { + StatusBar.setBarStyle('light-content'); this.refreshFunction(); }} onWillBlur={() => this.onWillBlur()} @@ -311,7 +372,7 @@ export default class WalletTransactions extends Component { style={{ alignSelf: 'flex-end', right: 10, flexDirection: 'row' }} onPress={() => { console.log('navigating to', this.state.wallet.getLabel()); - navigate('ManageFunds', { fromSecret: this.state.wallet.getSecret() }); + navigate('ManageFunds', { fromWallet: this.state.wallet }); }} > {loc.lnd.title} @@ -378,7 +439,6 @@ export default class WalletTransactions extends Component { } refreshControl={ this.refreshTransactions()} refreshing={this.state.isTransactionsLoading} />} data={this.state.dataSource} - extraData={this.state.dataSource} keyExtractor={this._keyExtractor} renderItem={rowData => { return ( @@ -409,6 +469,27 @@ export default class WalletTransactions extends Component { ); } + if (rowData.item.type === 'user_invoice' || rowData.item.type === 'payment_request') { + if (!rowData.item.ispaid) { + const currentDate = new Date(); + const now = (currentDate.getTime() / 1000) | 0; + const invoiceExpiration = rowData.item.timestamp + rowData.item.expire_time; + if (invoiceExpiration < now) { + return ( + + + + ); + } + } else { + return ( + + + + ); + } + } + if (!rowData.item.confirmations) { return ( @@ -440,6 +521,15 @@ export default class WalletTransactions extends Component { navigate('TransactionDetails', { hash: rowData.item.hash, }); + } else if ( + rowData.item.type === 'user_invoice' || + rowData.item.type === 'payment_request' || + rowData.item.type === 'paid_invoice' + ) { + this.props.navigation.navigate('LNDViewExistingInvoice', { + invoice: rowData.item, + fromWallet: this.state.wallet, + }); } }} badge={{ @@ -448,20 +538,8 @@ export default class WalletTransactions extends Component { containerStyle: { marginTop: 0 }, }} hideChevron - rightTitle={loc - .formatBalanceWithoutSuffix( - (rowData.item.value && rowData.item.value) || 0, - this.state.wallet.getPreferredBalanceUnit(), - ) - .toString()} - rightTitleStyle={{ - fontWeight: '600', - fontSize: 16, - color: - rowData.item.value / 100000000 < 0 || rowData.item.type === 'paid_invoice' - ? BlueApp.settings.foregroundColor - : '#37c0a1', - }} + rightTitle={this.rowTitle(rowData.item)} + rightTitleStyle={this.rowTitleStyle(rowData.item)} /> ); }} @@ -474,7 +552,9 @@ export default class WalletTransactions extends Component { backgroundColor: 'transparent', position: 'absolute', bottom: 30, - borderRadius: 15, + borderRadius: 30, + minHeight: 48, + flex: 0.84, overflow: 'hidden', }} > @@ -483,9 +563,10 @@ export default class WalletTransactions extends Component { return ( { - navigate('ReceiveDetails', { address: this.state.wallet.getAddress(), secret: this.state.wallet.getSecret() }); - if (this.state.wallet.getAddress()) { - // EV(EV.enum.RECEIVE_ADDRESS_CHANGED, w.getAddress()); + if (this.state.wallet.type === new LightningCustodianWallet().type) { + navigate('LNDCreateInvoice', { fromWallet: this.state.wallet }); + } else { + navigate('ReceiveDetails', { address: this.state.wallet.getAddress(), secret: this.state.wallet.getSecret() }); } }} /> @@ -515,7 +596,7 @@ export default class WalletTransactions extends Component { { console.log('navigating to', this.state.wallet.getLabel()); - navigate('ManageFunds', { fromSecret: this.state.wallet.getSecret() }); + navigate('ManageFunds', { fromWallet: this.state.wallet }); }} /> ); diff --git a/screen/wallets/xpub.js b/screen/wallets/xpub.js index a329263f3..cb5b3aa8e 100644 --- a/screen/wallets/xpub.js +++ b/screen/wallets/xpub.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { Dimensions, Platform, ActivityIndicator, View, Clipboard, Animated, TouchableOpacity } from 'react-native'; import { QRCode as QRSlow } from 'react-native-custom-qr-codes'; -import { BlueSpacing40, SafeBlueArea, BlueCard, BlueText, BlueHeaderDefaultSub } from '../../BlueComponents'; +import { BlueSpacing40, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle } from '../../BlueComponents'; import PropTypes from 'prop-types'; const QRFast = require('react-native-qrcode'); /** @type {AppStorage} */ @@ -17,11 +17,11 @@ if (aspectRatio > 1.6) { } export default class WalletXpub extends Component { - static navigationOptions = { - header: ({ navigation }) => { - return navigation.goBack(null)} />; - }, - }; + static navigationOptions = ({ navigation }) => ({ + ...BlueNavigationStyle(navigation, true), + title: loc.wallets.xpub.title, + headerLeft: null, + }); constructor(props) { super(props);