diff --git a/App.js b/App.js new file mode 100644 index 00000000..6799378f --- /dev/null +++ b/App.js @@ -0,0 +1,61 @@ +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 { + navigator = null; + + componentDidMount() { + Linking.getInitialURL() + .then(url => this.handleOpenURL({ url })) + .catch(console.error); + + Linking.addEventListener('url', this.handleOpenURL); + } + + componentWillUnmount() { + Linking.removeEventListener('url', this.handleOpenURL); + } + + handleOpenURL = event => { + if (event.url === null) { + return; + } + if (typeof event.url !== 'string') { + return; + } + if (event.url.indexOf('bitcoin:') === 0 || event.url.indexOf('BITCOIN:') === 0) { + this.navigator && + this.navigator.dispatch( + NavigationActions.navigate({ + routeName: 'SendDetails', + params: { + uri: event.url, + }, + }), + ); + } else if (event.url.indexOf('lightning:') === 0 || event.url.indexOf('LIGHTNING:') === 0) { + this.navigator && + this.navigator.dispatch( + NavigationActions.navigate({ + routeName: 'ScanLndInvoice', + params: { + uri: event.url, + }, + }), + ); + } + }; + + render() { + return ( + { + this.navigator = nav; + }} + /> + ); + } +} diff --git a/BlueComponents.js b/BlueComponents.js index 3e1c969e..21ccdc35 100644 --- a/BlueComponents.js +++ b/BlueComponents.js @@ -14,6 +14,7 @@ import { SafeAreaView, Clipboard, Platform, + TextInput, } from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import { WatchOnlyWallet, LegacyWallet } from './class'; @@ -281,25 +282,25 @@ export class BlueFormInput extends Component { export class BlueFormMultiInput extends Component { render() { return ( - ); } @@ -1047,7 +1048,7 @@ export class WalletsCarousel extends Component { color: '#fff', }} > - {loc.formatBalance(item.getBalance())} + {loc.formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit())} + + + + + + diff --git a/class/abstract-hd-wallet.js b/class/abstract-hd-wallet.js index 584960ea..1496a08d 100644 --- a/class/abstract-hd-wallet.js +++ b/class/abstract-hd-wallet.js @@ -277,7 +277,7 @@ export class AbstractHDWallet extends LegacyWallet { tx.value = value; // new BigNumber(value).div(100000000).toString() * 1; if (!tx.confirmations && latestBlock) { - tx.confirmations = latestBlock - tx.block_height; + tx.confirmations = latestBlock - tx.block_height + 1; } this.transactions.push(tx); @@ -324,7 +324,7 @@ export class AbstractHDWallet extends LegacyWallet { } } - // no luck - lets iterate over all addressess we have up to first unused address index + // no luck - lets iterate over all addresses we have up to first unused address index for (let c = 0; c <= this.next_free_change_address_index + 3; c++) { let possibleAddress = this._getInternalAddressByIndex(c); if (possibleAddress === address) { diff --git a/class/abstract-wallet.js b/class/abstract-wallet.js index 2b786440..5fc18df4 100644 --- a/class/abstract-wallet.js +++ b/class/abstract-wallet.js @@ -1,3 +1,5 @@ +import { BitcoinUnit } from '../models/bitcoinUnits'; + export class AbstractWallet { constructor() { this.type = 'abstract'; @@ -10,6 +12,7 @@ export class AbstractWallet { this.utxo = []; this._lastTxFetch = 0; this._lastBalanceFetch = 0; + this.preferredBalanceUnit = BitcoinUnit.BTC; } getTransactions() { @@ -32,6 +35,15 @@ export class AbstractWallet { return this.balance; } + getPreferredBalanceUnit() { + for (let value of Object.values(BitcoinUnit)) { + if (value === this.preferredBalanceUnit) { + return this.preferredBalanceUnit; + } + } + return BitcoinUnit.BTC; + } + allowReceive() { return true; } diff --git a/class/app-storage.js b/class/app-storage.js index 00b34e98..9b2ea4d4 100644 --- a/class/app-storage.js +++ b/class/app-storage.js @@ -16,6 +16,7 @@ export class AppStorage { static LANG = 'lang'; static CURRENCY = 'currency'; static LNDHUB = 'lndhub'; + static PREFERREDCURRENCY = 'preferredCurrency'; constructor() { /** {Array.} */ diff --git a/class/hd-segwit-p2sh-wallet.js b/class/hd-segwit-p2sh-wallet.js index 4619fd59..c1cafa8c 100644 --- a/class/hd-segwit-p2sh-wallet.js +++ b/class/hd-segwit-p2sh-wallet.js @@ -236,7 +236,7 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet { tx.value = value; // new BigNumber(value).div(100000000).toString() * 1; if (response.body.hasOwnProperty('info')) { if (response.body.info.latest_block.height && tx.block_height) { - tx.confirmations = response.body.info.latest_block.height - tx.block_height; + tx.confirmations = response.body.info.latest_block.height - tx.block_height + 1; } else { tx.confirmations = 0; } diff --git a/class/legacy-wallet.js b/class/legacy-wallet.js index 16f49ca6..40a74b59 100644 --- a/class/legacy-wallet.js +++ b/class/legacy-wallet.js @@ -9,7 +9,7 @@ const bitcoin = require('bitcoinjs-lib'); const signer = require('../models/signer'); /** - * Has private key and address signle like "1ABCD....." + * Has private key and single address like "1ABCD....." * (legacy P2PKH compressed) */ export class LegacyWallet extends AbstractWallet { diff --git a/class/lightning-custodian-wallet.js b/class/lightning-custodian-wallet.js index 6a21231f..274e3e15 100644 --- a/class/lightning-custodian-wallet.js +++ b/class/lightning-custodian-wallet.js @@ -1,5 +1,6 @@ import { LegacyWallet } from './legacy-wallet'; import Frisbee from 'frisbee'; +import { BitcoinUnit } from '../models/bitcoinUnits'; let BigNumber = require('bignumber.js'); export class LightningCustodianWallet extends LegacyWallet { @@ -15,6 +16,7 @@ export class LightningCustodianWallet extends LegacyWallet { this.refill_addressess = []; this.pending_transactions_raw = []; this.info_raw = false; + this.preferredBalanceUnit = BitcoinUnit.SATS; } /** diff --git a/currency.js b/currency.js index bdbea1f9..f8afcd9c 100644 --- a/currency.js +++ b/currency.js @@ -1,29 +1,38 @@ import Frisbee from 'frisbee'; import { AsyncStorage } from 'react-native'; import { AppStorage } from './class'; +import { FiatUnit } from './models/fiatUnit'; let BigNumber = require('bignumber.js'); - +let preferredFiatCurrency = FiatUnit.USD; let lang = {}; // let btcusd = 6500; // default const STRUCT = { LAST_UPDATED: 'LAST_UPDATED', BTC_USD: 'BTC_USD', + BTC_EUR: 'BTC_EUR', }; 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) { // not updating too often return; } - let json; try { const api = new Frisbee({ baseURI: 'https://www.bitstamp.net', }); - - let response = await api.get('/api/v2/ticker/btcusd'); + let response = await api.get('/api/v2/ticker/' + preferredFiatCurrency.endPointKey); json = response.body; if (typeof json === 'undefined' || typeof json.last === 'undefined') { throw new Error('Could not update currency rate: ' + response.err); @@ -34,11 +43,24 @@ async function updateExchangeRate() { } lang[STRUCT.LAST_UPDATED] = +new Date(); - lang[STRUCT.BTC_USD] = json.last * 1; + lang[STRUCT[preferredFiatCurrency.storageKey]] = json.last * 1; await AsyncStorage.setItem(AppStorage.CURRENCY, JSON.stringify(lang)); } -async function startUpdater() { +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; + } + } lang = await AsyncStorage.getItem(AppStorage.CURRENCY); try { lang = JSON.parse(lang); @@ -46,25 +68,35 @@ async function startUpdater() { lang = {}; } lang = lang || {}; - lang[STRUCT.LAST_UPDATED] = lang[STRUCT.LAST_UPDATED] || 0; - lang[STRUCT.BTC_USD] = lang[STRUCT.BTC_USD] || 6500; - + lang[STRUCT[preferredFiatCurrency.storageKey]] = lang[STRUCT[preferredFiatCurrency.storageKey]] || 6500; setInterval(() => updateExchangeRate(), 2 * 60 * 100); return updateExchangeRate(); } function satoshiToLocalCurrency(satoshi) { - if (!lang[STRUCT.BTC_USD]) return satoshi; + if (!lang[STRUCT[preferredFiatCurrency.storageKey]]) return satoshi; let b = new BigNumber(satoshi); b = b .dividedBy(100000000) - .multipliedBy(lang[STRUCT.BTC_USD]) + .multipliedBy(lang[STRUCT[preferredFiatCurrency.storageKey]]) .toString(10); b = parseFloat(b).toFixed(2); - return '$' + b; + const formatter = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: preferredFiatCurrency.formatterValue, + minimumFractionDigits: 2, + }); + return formatter.format(b); +} + +function BTCToLocalCurrency(bitcoin) { + let sat = new BigNumber(bitcoin); + sat = sat.multipliedBy(100000000).toNumber(); + + return satoshiToLocalCurrency(sat); } function satoshiToBTC(satoshi) { @@ -78,3 +110,4 @@ module.exports.startUpdater = startUpdater; module.exports.STRUCT = STRUCT; module.exports.satoshiToLocalCurrency = satoshiToLocalCurrency; module.exports.satoshiToBTC = satoshiToBTC; +module.exports.BTCToLocalCurrency = BTCToLocalCurrency; diff --git a/index.js b/index.js index f37414a2..2e420160 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,8 @@ +import 'intl'; +import 'intl/locale-data/jsonp/en'; import React from 'react'; import './shim.js'; -import MainBottomTabs from './MainBottomTabs'; +import App from './App'; import { Sentry } from 'react-native-sentry'; import { AppRegistry } from 'react-native'; import WalletMigrate from './screen/wallets/walletMigrate'; @@ -28,7 +30,7 @@ class BlueAppComponent extends React.Component { } render() { - return this.state.isMigratingData ? this.setIsMigratingData()} /> : ; + return this.state.isMigratingData ? this.setIsMigratingData()} /> : ; } } diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index c0693cab..b83964a3 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -1910,6 +1910,7 @@ "$(SRCROOT)/../node_modules/react-native-fs/**", ); INFOPLIST_FILE = BlueWallet/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( "$(inherited)", @@ -1946,6 +1947,7 @@ "$(SRCROOT)/../node_modules/react-native-fs/**", ); INFOPLIST_FILE = BlueWallet/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/ios/BlueWallet/AppDelegate.m b/ios/BlueWallet/AppDelegate.m index fff107df..1dea989e 100644 --- a/ios/BlueWallet/AppDelegate.m +++ b/ios/BlueWallet/AppDelegate.m @@ -7,6 +7,7 @@ #import "AppDelegate.h" +#import #import #import #if __has_include() @@ -39,4 +40,11 @@ RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation return YES; } +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication annotation:(id)annotation +{ + return [RCTLinkingManager application:application openURL:url + sourceApplication:sourceApplication annotation:annotation]; +} + @end diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index b3585042..8db7e1e7 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -17,11 +17,25 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.3.0 + 3.3.1 CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + bitcoin + lightning + + + CFBundleVersion - 151 + 168 + ITSAppUsesNonExemptEncryption + LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/loc/en.js b/loc/en.js index 13b83c11..db0923f9 100644 --- a/loc/en.js +++ b/loc/en.js @@ -10,6 +10,7 @@ module.exports = { never: 'never', }, wallets: { + select_wallet: 'Select Wallet', options: 'options', list: { app_name: 'Blue Wallet', @@ -34,6 +35,7 @@ module.exports = { scan: 'Scan', create: 'Create', label_new_segwit: 'New SegWit', + label_new_lightning: 'New Lightning', wallet_name: 'wallet name', wallet_type: 'type', or: 'or', diff --git a/loc/es.js b/loc/es.js index b90fb39a..4761eb50 100644 --- a/loc/es.js +++ b/loc/es.js @@ -11,6 +11,7 @@ module.exports = { }, wallets: { options: 'opciones', + select_wallet: 'Selecciona billetera', list: { app_name: 'Blue Wallet', title: 'billeteras', @@ -34,6 +35,7 @@ module.exports = { scan: 'Escaniar', create: 'Crear', label_new_segwit: 'Nuevo SegWit', + label_new_lightning: 'Nuevo Lightning', wallet_name: 'nombre de billetera', wallet_type: 'tipo de billetera', or: 'o', diff --git a/loc/index.js b/loc/index.js index d4305b3e..9ed26d91 100644 --- a/loc/index.js +++ b/loc/index.js @@ -2,8 +2,8 @@ import Localization from 'react-localization'; import { AsyncStorage } from 'react-native'; import { AppStorage } from '../class'; import { BitcoinUnit } from '../models/bitcoinUnits'; -let currency = require('../currency'); -let BigNumber = require('bignumber.js'); +const currency = require('../currency'); +const BigNumber = require('bignumber.js'); let strings; // first-time loading sequence @@ -63,50 +63,56 @@ strings.transactionTimeToReadable = function(time) { } }; +function removeTrailingZeros(value) { + value = value.toString(); + + if (value.indexOf('.') === -1) { + return value; + } + while ((value.slice(-1) === '0' || value.slice(-1) === '.') && value.indexOf('.') !== -1) { + value = value.substr(0, value.length - 1); + } + return value; +} + /** * * @param balance {Number} Float amount of bitcoins - * @param unit {String} Value from models/bitcoinUnits.js + * @param toUnit {String} Value from models/bitcoinUnits.js * @returns {string} */ -strings.formatBalance = (balance, unit) => { - const conversion = 100000000; - if (unit === undefined) { +strings.formatBalance = (balance, toUnit) => { + if (toUnit === undefined) { return balance + ' ' + BitcoinUnit.BTC; - } else { - if (balance !== 0) { - let b = new BigNumber(balance); - if (unit === BitcoinUnit.MBTC) { - return b.multipliedBy(1000).toString() + ' ' + BitcoinUnit.MBTC; - } else if (unit === BitcoinUnit.BITS) { - return b.multipliedBy(1000000).toString() + ' ' + BitcoinUnit.BITS; - } else if (unit === BitcoinUnit.SATOSHIS) { - return (b.times(conversion).toString() + ' ' + BitcoinUnit.SATOSHIS).replace(/\./g, ''); - } else if (unit === BitcoinUnit.SATS) { - return (b.times(conversion).toString() + ' ' + BitcoinUnit.SATS).replace(/\./g, ''); - } else if (unit === BitcoinUnit.LOCAL_CURRENCY) { - return currency.satoshiToLocalCurrency(b.times(conversion).toNumber()); - } - } + } + if (toUnit === BitcoinUnit.BTC) { return balance + ' ' + BitcoinUnit.BTC; + } else if (toUnit === BitcoinUnit.SATS) { + const value = new BigNumber(balance).multipliedBy(0.001); + return parseInt(value.toString().replace('.', '')).toString() + ' ' + BitcoinUnit.SATS; + } else if (toUnit === BitcoinUnit.LOCAL_CURRENCY) { + return currency.BTCToLocalCurrency(balance); } }; -strings.formatBalanceWithoutSuffix = (balance, unit) => { - const conversion = 100000000; +/** + * + * @param balance {Integer} Satoshis + * @param toUnit {String} Value from models/bitcoinUnits.js + * @returns {string} + */ +strings.formatBalanceWithoutSuffix = (balance, toUnit) => { + if (toUnit === undefined) { + return balance; + } if (balance !== 0) { - let b = new BigNumber(balance); - if (unit === BitcoinUnit.BTC) { - return Number(b.div(conversion)); - } else if (unit === BitcoinUnit.MBTC) { - return b.multipliedBy(1000).toString(); - } else if (unit === BitcoinUnit.BITS) { - return b.multipliedBy(1000000).toString(); - } else if (unit === BitcoinUnit.SATOSHIS) { - return b - .times(conversion) - .toString() - .replace(/\./g, ''); + if (toUnit === BitcoinUnit.BTC) { + const value = new BigNumber(balance).dividedBy(100000000).toFixed(8); + return removeTrailingZeros(value); + } else if (toUnit === BitcoinUnit.SATS) { + return balance; + } else if (toUnit === BitcoinUnit.LOCAL_CURRENCY) { + return currency.satoshiToLocalCurrency(balance); } } return balance; diff --git a/loc/pt_BR.js b/loc/pt_BR.js index 789d8cff..00e3d88e 100644 --- a/loc/pt_BR.js +++ b/loc/pt_BR.js @@ -11,6 +11,7 @@ module.exports = { }, wallets: { options: 'options', + select_wallet: 'Select Wallet', list: { tabBarLabel: 'Wallets', app_name: 'Blue Wallet', @@ -35,6 +36,7 @@ module.exports = { scan: 'Scanear', create: 'Criar', label_new_segwit: 'Novo SegWit', + label_new_lightning: 'Novo Lightning', wallet_name: 'Nome', wallet_type: 'Tipo', or: 'ou', diff --git a/loc/pt_PT.js b/loc/pt_PT.js index 592b566d..08309f8f 100644 --- a/loc/pt_PT.js +++ b/loc/pt_PT.js @@ -11,6 +11,7 @@ module.exports = { }, wallets: { options: 'options', + select_wallet: 'Select Wallet', list: { app_name: 'Blue Wallet', title: 'wallets', @@ -34,6 +35,7 @@ module.exports = { scan: 'Scan', create: 'Adicionar', label_new_segwit: 'Novo SegWit', + label_new_lightning: 'Novo Lightning', wallet_name: 'nome', wallet_type: 'tipo', or: 'ou', diff --git a/loc/ru.js b/loc/ru.js index e9e1c3a1..ece81d44 100644 --- a/loc/ru.js +++ b/loc/ru.js @@ -11,6 +11,7 @@ module.exports = { }, wallets: { options: 'options', + select_wallet: 'Select Wallet', list: { app_name: 'BlueWallet', title: 'кошельки', @@ -34,6 +35,7 @@ module.exports = { scan: 'Отсканировать', create: 'Создать', label_new_segwit: 'Новый SegWit', + label_new_lightning: 'Новый Lightning', wallet_name: 'имя кошелька', wallet_type: 'тип кошелька', or: 'or', diff --git a/loc/ua.js b/loc/ua.js index 9c2be6fb..f5b5bc2d 100644 --- a/loc/ua.js +++ b/loc/ua.js @@ -11,6 +11,7 @@ module.exports = { }, wallets: { options: 'options', + select_wallet: 'Select Wallet', list: { app_name: 'BlueWallet', title: 'гаманці', @@ -34,6 +35,7 @@ module.exports = { scan: 'Відсканувати', create: 'Створити', label_new_segwit: 'Новий SegWit', + label_new_lightning: 'Новий Lightning', wallet_name: "ім'я гаманця", wallet_type: 'тип гаманця', or: 'чи', diff --git a/models/bitcoinUnits.js b/models/bitcoinUnits.js index 84e12bb8..3b7e2d04 100644 --- a/models/bitcoinUnits.js +++ b/models/bitcoinUnits.js @@ -1,8 +1,5 @@ export const BitcoinUnit = Object.freeze({ BTC: 'BTC', - MBTC: 'mBTC', - BITS: 'bits', - SATOSHIS: 'satoshis', SATS: 'sats', LOCAL_CURRENCY: 'local_currency', }); diff --git a/models/fiatUnit.js b/models/fiatUnit.js new file mode 100644 index 00000000..184addff --- /dev/null +++ b/models/fiatUnit.js @@ -0,0 +1,4 @@ +export const FiatUnit = Object.freeze({ + USD: { endPointKey: 'btcusd', storageKey: 'BTC_USD', formatterValue: 'USD', symbol: '$' }, + EUR: { endPointKey: 'btceur', storageKey: 'BTC_EUR', formatterValue: 'EUR', symbol: '€' }, +}); diff --git a/package-lock.json b/package-lock.json index 460ed60f..d809dc5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -807,9 +807,9 @@ } }, "@babel/preset-env": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.2.0.tgz", - "integrity": "sha512-haGR38j5vOGVeBatrQPr3l0xHbs14505DcM57cbJy48kgMFvvHHoYEhHuRV+7vi559yyAUAVbTWzbK/B/pzJng==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.2.3.tgz", + "integrity": "sha512-AuHzW7a9rbv5WXmvGaPX7wADxFkZIqKlbBh1dmZUQp4iwiPpkE/Qnrji6SC4UQCQzvWY/cpHET29eUhXS9cLPw==", "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", @@ -896,24 +896,6 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz", - "integrity": "sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", - "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } - }, "@babel/plugin-transform-block-scoping": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz", @@ -924,9 +906,9 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.0.tgz", - "integrity": "sha512-aPCEkrhJYebDXcGTAP+cdUENkH7zqOlgbKwLbghjjHpJRJBWM/FSlCjMoPGA8oUdiMfOrk3+8EFPLLb5r7zj2w==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.2.2.tgz", + "integrity": "sha512-gEZvgTy1VtcDOaQty1l10T3jQmJKlNVxLDCs+3rCVPr6nMkODLELxViq5X9l+rfxbie3XrfrMCYYY6eX3aOcOQ==", "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", "@babel/helper-define-map": "^7.1.0", @@ -998,15 +980,6 @@ "@babel/helper-simple-access": "^7.1.0" } }, - "@babel/plugin-transform-object-super": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", - "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.1.0" - } - }, "@babel/plugin-transform-parameters": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.2.0.tgz", @@ -1026,9 +999,9 @@ } }, "@babel/plugin-transform-spread": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.0.tgz", - "integrity": "sha512-7TtPIdwjS/i5ZBlNiQePQCovDh9pAhVbp/nGVRBZuUdBiVRThyyLend3OHobc0G+RLCPPAN70+z/MAMhsgJd/A==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", "requires": { "@babel/helper-plugin-utils": "^7.0.0" } @@ -3015,13 +2988,13 @@ } }, "browserslist": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.5.tgz", - "integrity": "sha512-z9ZhGc3d9e/sJ9dIx5NFXkKoaiQTnrvrMsN3R1fGb1tkWWNSz12UewJn9TNxGo1l7J23h0MRaPmk7jfeTZYs1w==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", "requires": { - "caniuse-lite": "^1.0.30000912", - "electron-to-chromium": "^1.3.86", - "node-releases": "^1.0.5" + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" } }, "bs58": { @@ -3150,9 +3123,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000918", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000918.tgz", - "integrity": "sha512-CAZ9QXGViBvhHnmIHhsTPSWFBujDaelKnUj7wwImbyQRxmXynYqKGi3UaZTSz9MoVh+1EVxOS/DFIkrJYgR3aw==" + "version": "1.0.30000923", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000923.tgz", + "integrity": "sha512-j5ur7eeluOFjjPUkydtXP4KFAsmH3XaQNch5tvWSO+dLHYt5PE+VgJZLWtbVOodfWij6m6zas28T4gB/cLYq1w==" }, "capture-exit": { "version": "1.2.0", @@ -3942,9 +3915,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.90", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.90.tgz", - "integrity": "sha512-IjJZKRhFbWSOX1w0sdIXgp4CMRguu6UYcTckyFF/Gjtemsu/25eZ+RXwFlV+UWcIueHyQA1UnRJxocTpH5NdGA==" + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.96.tgz", + "integrity": "sha512-ZUXBUyGLeoJxp4Nt6G/GjBRLnyz8IKQGexZ2ndWaoegThgMGFO1tdDYID5gBV32/1S83osjJHyfzvanE/8HY4Q==" }, "elliptic": { "version": "6.4.1", @@ -5818,6 +5791,11 @@ } } }, + "intl": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/intl/-/intl-1.2.5.tgz", + "integrity": "sha1-giRKIZDE5Bn4Nx9ao02qNCDiq94=" + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -9374,9 +9352,9 @@ } }, "node-releases": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.1.tgz", - "integrity": "sha512-2UXrBr6gvaebo5TNF84C66qyJJ6r0kxBObgZIDX3D3/mt1ADKiHux3NJPWisq0wxvJJdkjECH+9IIKYViKj71Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.2.tgz", + "integrity": "sha512-j1gEV/zX821yxdWp/1vBMN0pSUjuH9oGUdLCb4PfUko6ZW7KdRs3Z+QGGwDUhYtSpQvdVVyLd2V0YvLsmdg5jQ==", "requires": { "semver": "^5.3.0" } @@ -10337,14 +10315,25 @@ "integrity": "sha512-vChdOL+yzecfnGA+B5EhEZkJ3kY3KlMzxEhShKh6Vdtooyl0yZfYNFQfYzgMf2v4pyQa+OTZ5esTxxgOOZDHqw==" }, "react": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz", - "integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.7.0.tgz", + "integrity": "sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.11.2" + "scheduler": "^0.12.0" + }, + "dependencies": { + "scheduler": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.12.0.tgz", + "integrity": "sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + } } }, "react-addons-shallow-compare": { @@ -10547,11 +10536,10 @@ } }, "react-native-camera": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/react-native-camera/-/react-native-camera-1.6.1.tgz", - "integrity": "sha512-CqC+zXG2ocy6xrSTOh5HNWfrnQ/50PUTc0zrrZNHMvAEVJpxVdp2XPK+a1VrBQDX3fTG2sy0lGOlh/L39U5PXg==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/react-native-camera/-/react-native-camera-1.6.4.tgz", + "integrity": "sha512-aS77+UVOKwxE+gTfpkPwIVoyFPD7uFuLV6qzV2M9kwK/KklR7wjd594xvatX40GHfLRFRpuUn5HiJU5WnOovbQ==", "requires": { - "lodash": "^4.17.10", "prop-types": "^15.6.2" } }, @@ -10582,9 +10570,9 @@ } }, "react-native-custom-qr-codes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/react-native-custom-qr-codes/-/react-native-custom-qr-codes-1.0.2.tgz", - "integrity": "sha1-t9EipGMtJSsPdulLQIRnNCGs4EE=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-native-custom-qr-codes/-/react-native-custom-qr-codes-2.0.0.tgz", + "integrity": "sha512-tUQipLzDorDgv/gzuhAISSqCCNOTtM+QveAUTnV1deeEKDAtdfdzY9QJIqLZYmfYvusySpXOK1Sdu9X+6AR2Jg==", "requires": { "prop-types": "^15.5.10" } @@ -10611,18 +10599,18 @@ "integrity": "sha1-oBgDk8UxujR3cixuQqMc6xwRYjs=" }, "react-native-fs": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.12.1.tgz", - "integrity": "sha512-T0vR5c+3fPVr12o4UoKr+gMHVokbLmKFGg2x1rOprwjaoJpV75Tw8mC3RpUp1u9AZHzWC4WcomhfqAlfXqDUug==", + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.13.3.tgz", + "integrity": "sha512-B62LSSAEYQGItg7KVTzTVVCxezOYFBYp4DMVFbdoZUd1mZVFdqR2sy1HY1mye1VI/Lf3IbxSyZEQ0GmrrdwLjg==", "requires": { "base-64": "^0.1.0", "utf8": "^2.1.1" } }, "react-native-gesture-handler": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.0.11.tgz", - "integrity": "sha512-CNQqPXrLgAY/YsB0x7/1bDfRNEhKFo7cwWOLxA7Ug8iTanifek8cV4XvSFvixjbAz57cXpBhf79JwZsyc8XMHg==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.0.12.tgz", + "integrity": "sha512-Qp5FjRmqUFeCevSu2IYQG1Xw+YXZ9YOzqze/ZxaIvWzYAoKsRchlgHhNoxvCqElp/befrnVFIjAEQyUxcmBKJw==", "requires": { "hoist-non-react-statics": "^2.3.1", "invariant": "^2.2.2", @@ -10839,15 +10827,15 @@ } }, "react-navigation": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/react-navigation/-/react-navigation-3.0.8.tgz", - "integrity": "sha512-gU55gHwytRczQnOLatFyF89eI8bv8NivPVoe0cEU8sxCKvX2RbuElGtLxKPWKJiIGz4ZScrmNqiJpkjTsmwiTg==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/react-navigation/-/react-navigation-3.0.9.tgz", + "integrity": "sha512-SFVlL96HIjMW9JiRhqwSn6RDP6MqeRMZHgf+hK/RMwSWLyfdZZEPKm3K/y/g1mg3l4Na4T3qnRMheGJF2dD0zw==", "requires": { "@react-navigation/core": "3.0.2", "@react-navigation/native": "3.0.3", "react-navigation-drawer": "1.0.5", - "react-navigation-stack": "1.0.5", - "react-navigation-tabs": "1.0.1" + "react-navigation-stack": "1.0.6", + "react-navigation-tabs": "1.0.2" } }, "react-navigation-drawer": { @@ -10859,14 +10847,14 @@ } }, "react-navigation-stack": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/react-navigation-stack/-/react-navigation-stack-1.0.5.tgz", - "integrity": "sha512-X/rsSKD+dvfuDitmAJvqelRjD9hmA5SP7uq7F6CncaUX6M2BLb8Q39KBxcjsBMLVHOSrfOoUq3HciwN1xSBxvg==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/react-navigation-stack/-/react-navigation-stack-1.0.6.tgz", + "integrity": "sha512-7vnoceO6d/KYvtOSi3Ui3u1gvZEF/dBrOn+Gb1zqiZ3t+0oWRPpU36OmXAh/SwI5aokQyoihAlH9UBMfp+fbEA==" }, "react-navigation-tabs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-navigation-tabs/-/react-navigation-tabs-1.0.1.tgz", - "integrity": "sha512-XDftTg0sxh2ZMA4yJ4g8POCSova1gJM3heIUUup7/mDeUKcQRZzE9Xf9gQrbZteybJLAxATy+LAjaUpDvvdKmg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-navigation-tabs/-/react-navigation-tabs-1.0.2.tgz", + "integrity": "sha512-ffWPVdo+L0GLbQlLAzH7ITYqh9V9NdqT/juj8QtESH5/2yUqfvqTxQoSowvFIrtiIHHFH6tLoQy1sZZciTxmeg==", "requires": { "hoist-non-react-statics": "^2.5.0", "prop-types": "^15.6.1", @@ -10906,15 +10894,23 @@ } }, "react-test-renderer": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.6.3.tgz", - "integrity": "sha512-B5bCer+qymrQz/wN03lT0LppbZUDRq6AMfzMKrovzkGzfO81a9T+PWQW6MzkWknbwODQH/qpJno/yFQLX5IWrQ==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.7.0.tgz", + "integrity": "sha512-tFbhSjknSQ6+ttzmuGdv+SjQfmvGcq3PFKyPItohwhhOBmRoTf1We3Mlt3rJtIn85mjPXOkKV+TaKK4irvk9Yg==", "dev": true, "requires": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "react-is": "^16.6.3", - "scheduler": "^0.11.2" + "react-is": "^16.7.0", + "scheduler": "^0.12.0" + }, + "dependencies": { + "react-is": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.7.0.tgz", + "integrity": "sha512-Z0VRQdF4NPDoI0tsXVMLkJLiwEBa+RP66g0xDHxgxysxSoCUccSten4RTF/UFvZF1dZvZ9Zu1sx+MDXwcOR34g==", + "dev": true + } } }, "react-timer-mixin": { @@ -11730,9 +11726,10 @@ "integrity": "sha1-XWFr6KXmB9VOEUr65Vt+ry/MMkA=" }, "scheduler": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz", - "integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.12.0.tgz", + "integrity": "sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==", + "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/package.json b/package.json index f43a142f..31e3efbc 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "jest": "23.6.0", "metro-react-native-babel-preset": "^0.49.1", "prettier-eslint-cli": "^4.7.1", - "react-test-renderer": "^16.6.3", + "react-test-renderer": "^16.7.0", "rn-nodeify": "github:mvayngrib/rn-nodeify" }, "scripts": { @@ -35,7 +35,7 @@ } }, "dependencies": { - "@babel/preset-env": "^7.2.0", + "@babel/preset-env": "^7.2.3", "asyncstorage-down": "^3.1.1", "bignumber.js": "^7.0.0", "bip21": "^2.0.2", @@ -52,6 +52,7 @@ "eslint-plugin-standard": "^4.0.0", "events": "^1.1.1", "frisbee": "^1.6.4", + "intl": "^1.2.5", "isaac": "0.0.5", "mocha": "^5.2.0", "node-libs-react-native": "^1.0.1", @@ -59,16 +60,16 @@ "prettier": "^1.14.2", "process": "^0.11.10", "prop-types": "^15.6.2", - "react": "^16.6.3", + "react": "^16.7.0", "react-localization": "^1.0.10", "react-native": "^0.57.8", - "react-native-camera": "^1.6.1", - "react-native-custom-qr-codes": "^1.0.2", + "react-native-camera": "^1.6.4", + "react-native-custom-qr-codes": "^2.0.0", "react-native-device-info": "^0.24.3", "react-native-elements": "^0.19.0", "react-native-flexi-radio-button": "^0.2.2", - "react-native-fs": "^2.12.1", - "react-native-gesture-handler": "^1.0.11", + "react-native-fs": "^2.13.3", + "react-native-gesture-handler": "^1.0.12", "react-native-google-analytics-bridge": "^6.1.2", "react-native-haptic-feedback": "^1.4.2", "react-native-level-fs": "^3.0.1", @@ -84,7 +85,7 @@ "react-native-sortable-list": "0.0.22", "react-native-svg": "^8.0.10", "react-native-vector-icons": "^6.0.2", - "react-navigation": "^3.0.8", + "react-navigation": "^3.0.9", "react-test-render": "^1.1.1", "readable-stream": "^1.1.14", "request-promise-native": "^1.0.5", diff --git a/screen/lnd/scanLndInvoice.js b/screen/lnd/scanLndInvoice.js index 70e725c7..7d00a208 100644 --- a/screen/lnd/scanLndInvoice.js +++ b/screen/lnd/scanLndInvoice.js @@ -4,6 +4,7 @@ import { Text, Dimensions, ActivityIndicator, View, TouchableOpacity, TouchableW import { Icon } from 'react-native-elements'; import PropTypes from 'prop-types'; import { BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueHeaderDefaultSub } from '../../BlueComponents'; +import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; /** @type {AppStorage} */ let BlueApp = require('../../BlueApp'); let currency = require('../../currency'); @@ -28,6 +29,13 @@ export default class ScanLndInvoice extends React.Component { if (props.navigation.state.params.fromSecret) fromSecret = props.navigation.state.params.fromSecret; let fromWallet = {}; + if (!fromSecret) { + const lightningWallets = BlueApp.getWallets().filter(item => item.type === new LightningCustodianWallet().type); + if (lightningWallets.length > 0) { + fromSecret = lightningWallets[0].getSecret(); + } + } + for (let w of BlueApp.getWallets()) { if (w.getSecret() === fromSecret) { fromWallet = w; @@ -49,6 +57,10 @@ export default class ScanLndInvoice extends React.Component { }, true, ); + + if (this.props.navigation.state.params.uri) { + this.processTextForInvoice(this.props.navigation.getParam('uri')); + } } async processInvoice(data) { @@ -128,6 +140,14 @@ export default class ScanLndInvoice extends React.Component { 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 }); + } + }; + render() { return ( @@ -162,13 +182,7 @@ export default class ScanLndInvoice extends React.Component { }} > { - if (text.toLowerCase().startsWith('lnb')) { - this.processInvoice(text); - } else { - this.setState({ decoded: undefined, expiresIn: undefined }); - } - }} + onChangeText={this.processTextForInvoice} placeholder={loc.wallets.details.destination} numberOfLines={1} value={this.state.hasOwnProperty('decoded') && this.state.decoded !== undefined ? this.state.decoded.destination : ''} @@ -263,8 +277,10 @@ ScanLndInvoice.propTypes = { navigation: PropTypes.shape({ goBack: PropTypes.function, navigate: PropTypes.function, + getParam: PropTypes.function, state: PropTypes.shape({ params: PropTypes.shape({ + uri: PropTypes.string, fromSecret: PropTypes.string, }), }), diff --git a/screen/send/details.js b/screen/send/details.js index efaf17bf..bcd0d303 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -11,8 +11,9 @@ import { StyleSheet, Platform, Slider, + Text, } from 'react-native'; -import { Text, Icon } from 'react-native-elements'; +import { Icon } from 'react-native-elements'; import { BlueNavigationStyle, BlueButton } from '../../BlueComponents'; import PropTypes from 'prop-types'; import Modal from 'react-native-modal'; @@ -28,7 +29,6 @@ let BigNumber = require('bignumber.js'); let BlueApp = require('../../BlueApp'); let loc = require('../../loc'); let bitcoin = require('bitcoinjs-lib'); -let currency = require('../../currency'); const btcAddressRx = /^[a-zA-Z0-9]{26,35}$/; @@ -41,19 +41,19 @@ export default class SendDetails extends Component { constructor(props) { super(props); console.log('props.navigation.state.params=', props.navigation.state.params); - let startTime = Date.now(); let address; + let memo; if (props.navigation.state.params) address = props.navigation.state.params.address; - let memo = false; if (props.navigation.state.params) memo = props.navigation.state.params.memo; let fromAddress; if (props.navigation.state.params) fromAddress = props.navigation.state.params.fromAddress; let fromSecret; if (props.navigation.state.params) fromSecret = props.navigation.state.params.fromSecret; - let fromWallet = {}; + let fromWallet = null; - let startTime2 = Date.now(); - for (let w of BlueApp.getWallets()) { + const wallets = BlueApp.getWallets(); + + for (let w of wallets) { if (w.getSecret() === fromSecret) { fromWallet = w; break; @@ -64,72 +64,61 @@ export default class SendDetails extends Component { } } - let endTime2 = Date.now(); - console.log('getAddress() took', (endTime2 - startTime2) / 1000, 'sec'); - console.log({ memo }); + // fallback to first wallet if it exists + if (!fromWallet && wallets[0]) fromWallet = wallets[0]; this.state = { isFeeSelectionModalVisible: false, - fromAddress: fromAddress, - fromWallet: fromWallet, - fromSecret: fromSecret, + fromAddress, + fromWallet, + fromSecret, isLoading: true, - address: address, - amount: '', + address, memo, fee: 1, networkTransactionFees: new NetworkTransactionFee(1, 1, 1), feeSliderValue: 1, bip70TransactionExpiration: null, }; - - let endTime = Date.now(); - console.log('constructor took', (endTime - startTime) / 1000, 'sec'); } async componentDidMount() { - EV( - EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, - data => { - if (btcAddressRx.test(data) || data.indexOf('bc1') === 0) { - this.setState({ - address: data, - bip70TransactionExpiration: null, - }); - } else { - let address, options; - try { - const decoded = bip21.decode(data); - address = decoded.address; - options = decoded.options; - } catch (Err) { - console.log(Err); - } - console.log(options); - if (btcAddressRx.test(address)) { + EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, data => { + this.setState( + { isLoading: false }, + () => { + if (btcAddressRx.test(data) || data.indexOf('bc1') === 0) { this.setState({ - address, - amount: options.amount, - memo: options.label || options.message, + address: data, bip70TransactionExpiration: null, + isLoading: false, }); - } else if (BitcoinBIP70TransactionDecode.matchesPaymentURL(data)) { - BitcoinBIP70TransactionDecode.decode(data) - .then(response => { - this.setState({ - address: response.address, - amount: loc.formatBalanceWithoutSuffix(response.amount, BitcoinUnit.BTC), - memo: response.memo, - fee: response.fee, - bip70TransactionExpiration: response.expires, - }); - }) - .catch(error => alert(error.errorMessage)); + } else { + let address, options; + try { + const decoded = bip21.decode(data); + address = decoded.address; + options = decoded.options; + } catch (Err) { + console.log(Err); + } + console.log(options); + if (btcAddressRx.test(address)) { + this.setState({ + address, + amount: options.amount, + memo: options.label || options.message, + bip70TransactionExpiration: null, + isLoading: false, + }); + } else if (BitcoinBIP70TransactionDecode.matchesPaymentURL(data)) { + this.processBIP70Invoice(data); + } } - } - }, - true, - ); + }, + true, + ); + }); let recommendedFees = await NetworkTransactionFees.recommendedFees().catch(response => { this.setState({ fee: response.halfHourFee, @@ -145,11 +134,29 @@ export default class SendDetails extends Component { feeSliderValue: recommendedFees.halfHourFee, isLoading: false, }); + + if (this.props.navigation.state.params.uri) { + if (BitcoinBIP70TransactionDecode.matchesPaymentURL(this.props.navigation.state.params.uri)) { + this.processBIP70Invoice(this.props.navigation.state.params.uri); + } else { + try { + let amount = ''; + let parsedBitcoinUri = null; + let address = ''; + 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; + this.setState({ address, amount, memo }); + } catch (error) { + console.log(error); + alert('Error: Unable to decode Bitcoin address'); + } + } + } } - let startTime = Date.now(); - console.log('send/details - componentDidMount'); - let endTime = Date.now(); - console.log('componentDidMount took', (endTime - startTime) / 1000, 'sec'); } recalculateAvailableBalance(balance, amount, fee) { @@ -194,6 +201,40 @@ export default class SendDetails extends Component { return new BigNumber(totalInput - totalOutput).dividedBy(100000000).toNumber(); } + processBIP70Invoice(text) { + try { + if (BitcoinBIP70TransactionDecode.matchesPaymentURL(text)) { + this.setState( + { + isLoading: true, + }, + () => { + Keyboard.dismiss(); + BitcoinBIP70TransactionDecode.decode(text) + .then(response => { + this.setState({ + address: response.address, + amount: loc.formatBalanceWithoutSuffix(response.amount, BitcoinUnit.BTC), + memo: response.memo, + fee: response.fee, + bip70TransactionExpiration: response.expires, + isLoading: false, + }); + }) + .catch(error => { + alert(error.errorMessage); + this.setState({ address: text.replace(' ', ''), isLoading: false, bip70TransactionExpiration: null, amount: 0 }); + }); + }, + ); + } + return true; + } catch (error) { + this.setState({ address: text.replace(' ', ''), isLoading: false, bip70TransactionExpiration: null, amount: 0 }); + return false; + } + } + async createTransaction() { this.setState({ isLoading: true }); let error = false; @@ -319,6 +360,12 @@ export default class SendDetails extends Component { }); } + onWalletSelect = wallet => { + this.setState({ fromAddress: wallet.getAddress(), fromSecret: wallet.getSecret(), fromWallet: wallet }, () => + this.props.navigation.goBack(null), + ); + }; + renderFeeSelectionModal = () => { return ( { + return ( + + {!this.state.isLoading && ( + this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect })} + > + + {loc.wallets.select_wallet.toLowerCase()} + + + + )} + + {this.state.fromWallet.getLabel()} + + {this.state.fromWallet.getBalance()} + + + {BitcoinUnit.BTC} + + + + ); + }; + render() { if (!this.state.fromWallet.getAddress) { return ( @@ -408,159 +482,145 @@ export default class SendDetails extends Component { return ( - - - - this.setState({ amount: text.replace(',', '.') })} - placeholder="0" - maxLength={10} - editable={!this.state.isLoading} - value={this.state.amount + ''} - placeholderTextColor="#0f5cc0" - style={{ - color: '#0f5cc0', - fontSize: 36, - fontWeight: '600', - }} - /> - - {' ' + BitcoinUnit.BTC} - - - - - {currency.satoshiToLocalCurrency(loc.formatBalanceWithoutSuffix(this.state.amount || 0, BitcoinUnit.SATOSHIS))} - - - - { - if (BitcoinBIP70TransactionDecode.matchesPaymentURL(text)) { - this.setState( - { - isLoading: true, - }, - () => { - Keyboard.dismiss(); - BitcoinBIP70TransactionDecode.decode(text).then(response => { - this.setState({ - address: response.address, - amount: loc.formatBalanceWithoutSuffix(response.amount, BitcoinUnit.BTC), - memo: response.memo, - fee: response.fee, - bip70TransactionExpiration: response.expires, - isLoading: false, - }); - }); - }, - ); - } else { - this.setState({ address: text.replace(' ', ''), isLoading: false, bip70TransactionExpiration: null }); - } - }} - placeholder={loc.send.details.address} - numberOfLines={1} - value={this.state.address} - style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} - editable={!this.state.isLoading} - /> - this.props.navigation.navigate('ScanQrAddress')} + + + + + this.setState({ amount: text.replace(',', '.') })} + placeholder="0" + maxLength={10} + editable={!this.state.isLoading} + value={this.state.amount} + placeholderTextColor="#0f5cc0" + style={{ + color: '#0f5cc0', + fontSize: 36, + fontWeight: '600', + }} + /> + + {' ' + BitcoinUnit.BTC} + + + + + {loc.formatBalance(this.state.amount || 0, BitcoinUnit.LOCAL_CURRENCY)} + + + - - {loc.send.details.scan} - - - - this.setState({ memo: text })} - placeholder={loc.send.details.note_placeholder} - value={this.state.memo} - numberOfLines={1} - style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} - editable={!this.state.isLoading} - /> - - this.setState({ isFeeSelectionModalVisible: true })} - disabled={this.state.isLoading} - style={{ flexDirection: 'row', marginHorizontal: 20, justifyContent: 'space-between', alignItems: 'center' }} - > - Fee + { + if (!this.processBIP70Invoice(text)) { + this.setState({ address: text.replace(' ', ''), isLoading: false, bip70TransactionExpiration: null }); + } else { + this.setState({ address: text.replace(' ', ''), isLoading: false, bip70TransactionExpiration: null }); + } + }} + placeholder={loc.send.details.address} + numberOfLines={1} + value={this.state.address} + style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} + editable={!this.state.isLoading} + /> + this.props.navigation.navigate('ScanQrAddress')} + style={{ + width: 75, + height: 36, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + backgroundColor: '#bebebe', + borderRadius: 4, + paddingVertical: 4, + paddingHorizontal: 8, + marginHorizontal: 4, + }} + > + + {loc.send.details.scan} + + - {this.state.fee} - sat/b + this.setState({ memo: text })} + placeholder={loc.send.details.note_placeholder} + value={this.state.memo} + numberOfLines={1} + style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} + editable={!this.state.isLoading} + /> - - {this.renderCreateButton()} - {this.renderFeeSelectionModal()} - + this.setState({ isFeeSelectionModalVisible: true })} + disabled={this.state.isLoading} + style={{ flexDirection: 'row', marginHorizontal: 20, justifyContent: 'space-between', alignItems: 'center' }} + > + Fee + + {this.state.fee} + sat/b + + + {this.renderCreateButton()} + {this.renderFeeSelectionModal()} + + + {this.renderWalletSelectionButton()} ); @@ -605,6 +665,7 @@ SendDetails.propTypes = { satoshiPerByte: PropTypes.string, fromSecret: PropTypes.fromSecret, memo: PropTypes.string, + uri: PropTypes.string, }), }), }), diff --git a/screen/settings/currency.js b/screen/settings/currency.js new file mode 100644 index 00000000..40b62f9f --- /dev/null +++ b/screen/settings/currency.js @@ -0,0 +1,89 @@ +import React, { Component } from 'react'; +import { FlatList, TouchableOpacity, AsyncStorage, ActivityIndicator, View } from 'react-native'; +import { SafeBlueArea, BlueNavigationStyle, BlueListItem } 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'); + +export default class Currency extends Component { + static navigationOptions = () => ({ + ...BlueNavigationStyle(), + title: loc.settings.currency, + }); + + constructor(props) { + super(props); + this.state = { data: Object.values(FiatUnit), isSavingNewPreferredCurrency: false }; + } + + async componentDidMount() { + try { + const preferredCurrency = await AsyncStorage.getItem(AppStorage.PREFERREDCURRENCY); + if (preferredCurrency === null) { + throw Error(); + } + this.setState({ selectedCurrency: JSON.parse(preferredCurrency) }); + } catch (_error) { + this.setState({ selectedCurrency: FiatUnit.USD }); + } + } + + renderItem = ({ item }) => { + return ( + { + this.setState({ isSavingNewPreferredCurrency: true, selectedCurrency: item }, async () => { + await AsyncStorage.setItem(AppStorage.PREFERREDCURRENCY, JSON.stringify(item)); + await currency.startUpdater(true); + this.setState({ isSavingNewPreferredCurrency: false }); + }); + }} + > + + ) : ( + + ), + } + : { hideChevron: true })} + /> + + ); + }; + + render() { + if (this.state.selectedCurrency !== null && this.state.selectedCurrency !== undefined) { + return ( + + `${index}`} + data={this.state.data} + extraData={this.state.data} + renderItem={this.renderItem} + /> + + ); + } + return ( + + + + ); + } +} + +Currency.propTypes = { + navigation: PropTypes.shape({ + navigate: PropTypes.func, + goBack: PropTypes.func, + }), +}; diff --git a/screen/settings/settings.js b/screen/settings/settings.js index bacbecd4..847193d5 100644 --- a/screen/settings/settings.js +++ b/screen/settings/settings.js @@ -44,6 +44,9 @@ export default class Settings extends Component { this.props.navigation.navigate('Language')}> + this.props.navigation.navigate('Currency')}> + + this.props.navigation.navigate('About')}> diff --git a/screen/transactions/details.js b/screen/transactions/details.js index 2e625017..a949a517 100644 --- a/screen/transactions/details.js +++ b/screen/transactions/details.js @@ -12,6 +12,7 @@ import { BlueNavigationStyle, } from '../../BlueComponents'; import PropTypes from 'prop-types'; +import { BitcoinUnit } from '../../models/bitcoinUnits'; /** @type {AppStorage} */ let BlueApp = require('../../BlueApp'); let loc = require('../../loc'); @@ -63,7 +64,6 @@ export default class TransactionsDetails extends Component { } } } - this.state = { isLoading: true, tx: foundTx, @@ -140,6 +140,15 @@ export default class TransactionsDetails extends Component { )} + {this.state.tx.hasOwnProperty('fee') && ( + + {loc.send.create.fee} + + {loc.formatBalance(this.state.tx.fee, BitcoinUnit.SATS, BitcoinUnit.BTC)} + + + )} + {this.state.tx.hasOwnProperty('hash') && ( @@ -160,6 +169,7 @@ export default class TransactionsDetails extends Component { )} + {this.state.tx.hasOwnProperty('received') && ( Received diff --git a/screen/wallets/add.js b/screen/wallets/add.js index 4dccf8ea..527401b1 100644 --- a/screen/wallets/add.js +++ b/screen/wallets/add.js @@ -95,7 +95,7 @@ export default class WalletsAdd extends Component { { this.setLabel(text); }} diff --git a/screen/wallets/details.js b/screen/wallets/details.js index da7858c5..f7bfaa7f 100644 --- a/screen/wallets/details.js +++ b/screen/wallets/details.js @@ -16,7 +16,7 @@ export default class WalletDetails extends Component { title: loc.wallets.details.title, headerRight: ( { navigation.getParam('saveAction')(); }} @@ -73,7 +73,6 @@ export default class WalletDetails extends Component { ); } - return ( @@ -128,11 +127,6 @@ export default class WalletDetails extends Component { this.props.navigation.navigate('WalletExport', { address: this.state.wallet.getAddress(), diff --git a/screen/wallets/import.js b/screen/wallets/import.js index e1dae48a..d6923864 100644 --- a/screen/wallets/import.js +++ b/screen/wallets/import.js @@ -58,9 +58,7 @@ export default class WalletsImport extends Component { await BlueApp.saveToDisk(); EV(EV.enum.WALLETS_COUNT_CHANGED); A(A.ENUM.CREATED_WALLET); - setTimeout(() => { - this.props.navigation.popToTop(); - }, 500); + this.props.navigation.dismiss(); } async importMnemonic(text) { @@ -217,7 +215,7 @@ export default class WalletsImport extends Component { { this.setLabel(text); }} @@ -261,7 +259,7 @@ export default class WalletsImport extends Component { WalletsImport.propTypes = { navigation: PropTypes.shape({ navigate: PropTypes.func, - popToTop: PropTypes.func, + dismiss: PropTypes.func, goBack: PropTypes.func, }), }; diff --git a/screen/wallets/list.js b/screen/wallets/list.js index 1f7b2c12..9150e329 100644 --- a/screen/wallets/list.js +++ b/screen/wallets/list.js @@ -15,8 +15,9 @@ import { } from '../../BlueComponents'; import { Icon } from 'react-native-elements'; import { NavigationEvents } from 'react-navigation'; +import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import PropTypes from 'prop-types'; -const BigNumber = require('bignumber.js'); +import { BitcoinUnit } from '../../models/bitcoinUnits'; let EV = require('../../events'); let A = require('../../analytics'); /** @type {AppStorage} */ @@ -32,7 +33,7 @@ export default class WalletsList extends Component { }, headerRight: ( navigation.navigate('Settings')} > @@ -215,6 +216,8 @@ export default class WalletsList extends Component { handleLongPress = () => { if (BlueApp.getWallets().length > 1) { this.props.navigation.navigate('ReorderWallets'); + } else { + ReactNativeHapticFeedback.trigger('notificationError', false); } }; @@ -342,11 +345,14 @@ export default class WalletsList extends Component { containerStyle: { marginTop: 0 }, }} hideChevron - rightTitle={new BigNumber((rowData.item.value && rowData.item.value) || 0).dividedBy(100000000).toString()} + rightTitle={loc.formatBalanceWithoutSuffix(rowData.item.value && rowData.item.value, BitcoinUnit.BTC)} rightTitleStyle={{ fontWeight: '600', fontSize: 16, - color: rowData.item.value / 100000000 < 0 ? BlueApp.settings.foregroundColor : '#37c0a1', + color: + rowData.item.value / 100000000 < 0 || rowData.item.type === 'paid_invoice' + ? BlueApp.settings.foregroundColor + : '#37c0a1', }} /> ); diff --git a/screen/wallets/scanQrWif.js b/screen/wallets/scanQrWif.js index f1359566..dc506abe 100644 --- a/screen/wallets/scanQrWif.js +++ b/screen/wallets/scanQrWif.js @@ -4,7 +4,7 @@ import { ActivityIndicator, Image, View, TouchableOpacity } from 'react-native'; import { BlueText, SafeBlueArea, BlueButton } from '../../BlueComponents'; import Camera from 'react-native-camera'; import Permissions from 'react-native-permissions'; -import {SegwitP2SHWallet, LegacyWallet, WatchOnlyWallet, HDLegacyP2PKHWallet} from '../../class'; +import { SegwitP2SHWallet, LegacyWallet, WatchOnlyWallet, HDLegacyP2PKHWallet } from '../../class'; import PropTypes from 'prop-types'; import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet'; import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; diff --git a/screen/wallets/selectWallet.js b/screen/wallets/selectWallet.js new file mode 100644 index 00000000..c99ba315 --- /dev/null +++ b/screen/wallets/selectWallet.js @@ -0,0 +1,190 @@ +import React, { Component } from 'react'; +import { View, ActivityIndicator, Image, Text, TouchableOpacity, FlatList } from 'react-native'; +import { SafeBlueArea, BlueNavigationStyle } from '../../BlueComponents'; +import LinearGradient from 'react-native-linear-gradient'; +import PropTypes from 'prop-types'; +import { WatchOnlyWallet, LegacyWallet } from '../../class'; +import { HDLegacyP2PKHWallet } from '../../class/hd-legacy-p2pkh-wallet'; +import { HDLegacyBreadwalletWallet } from '../../class/hd-legacy-breadwallet-wallet'; +import { HDSegwitP2SHWallet } from '../../class/hd-segwit-p2sh-wallet'; +import { LightningCustodianWallet } from '../../class/lightning-custodian-wallet'; +import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; +/** @type {AppStorage} */ +let BlueApp = require('../../BlueApp'); +let loc = require('../../loc'); + +export default class SelectWallet extends Component { + static navigationOptions = () => ({ + ...BlueNavigationStyle(), + title: loc.wallets.select_wallet, + }); + + constructor(props) { + super(props); + this.state = { + isLoading: true, + data: [], + }; + } + + componentDidMount() { + const wallets = BlueApp.getWallets().filter(item => item.type !== new LightningCustodianWallet().type); + this.setState({ + data: wallets, + isLoading: false, + }); + } + + _renderItem = ({ item }) => { + let gradient1 = '#65ceef'; + let gradient2 = '#68bbe1'; + + if (new WatchOnlyWallet().type === item.type) { + gradient1 = '#7d7d7d'; + gradient2 = '#4a4a4a'; + } + + if (new LegacyWallet().type === item.type) { + gradient1 = '#40fad1'; + gradient2 = '#15be98'; + } + + if (new HDLegacyP2PKHWallet().type === item.type) { + gradient1 = '#e36dfa'; + gradient2 = '#bd10e0'; + } + + if (new HDLegacyBreadwalletWallet().type === item.type) { + gradient1 = '#fe6381'; + gradient2 = '#f99c42'; + } + + if (new HDSegwitP2SHWallet().type === item.type) { + gradient1 = '#c65afb'; + gradient2 = '#9053fe'; + } + + if (new LightningCustodianWallet().type === item.type) { + gradient1 = '#f1be07'; + gradient2 = '#f79056'; + } + + return ( + { + ReactNativeHapticFeedback.trigger('selection', false); + this.props.navigation.getParam('onWalletSelect')(item); + }} + > + + + + + + + {item.getLabel()} + + + {loc.formatBalance(item.getBalance())} + + + + {loc.wallets.list.latest_transaction} + + + {loc.transactionTimeToReadable(item.getLatestTransactionTime())} + + + + + ); + }; + + render() { + if (this.state.isLoading || this.state.data.length <= 0) { + return ( + + + + ); + } + + return ( + + `${index}`} + /> + + ); + } +} + +SelectWallet.propTypes = { + navigation: PropTypes.shape({ + navigate: PropTypes.func, + setParams: PropTypes.func, + dismiss: PropTypes.func, + getParam: PropTypes.func, + }), +}; diff --git a/screen/wallets/transactions.js b/screen/wallets/transactions.js index fe59a15c..180cd587 100644 --- a/screen/wallets/transactions.js +++ b/screen/wallets/transactions.js @@ -3,7 +3,6 @@ import { Text, View, Image, FlatList, RefreshControl, TouchableOpacity } from 'r import LinearGradient from 'react-native-linear-gradient'; import PropTypes from 'prop-types'; import { NavigationEvents } from 'react-navigation'; -import { LightningCustodianWallet } from '../../class'; import { BlueText, BlueTransactionOnchainIcon, @@ -18,11 +17,11 @@ import { } 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'); -const BigNumber = require('bignumber.js'); let EV = require('../../events'); export default class WalletTransactions extends Component { @@ -30,7 +29,7 @@ export default class WalletTransactions extends Component { return { headerRight: ( navigation.navigate('WalletDetails', { address: navigation.state.params.wallet.getAddress(), @@ -38,11 +37,11 @@ export default class WalletTransactions extends Component { }) } > - {loc.wallets.options} + {loc.wallets.options} ), headerStyle: { - backgroundColor: navigation.getParam('gradients')[0], + backgroundColor: navigation.getParam('gradients')[0] || '#65ceef', borderBottomWidth: 0, elevation: 0, shadowRadius: 0, @@ -57,14 +56,13 @@ export default class WalletTransactions extends Component { // here, when we receive REMOTE_TRANSACTIONS_COUNT_CHANGED we fetch TXs and balance for current wallet EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED, this.refreshTransactionsFunction.bind(this)); const wallet = props.navigation.getParam('wallet'); - this.props.navigation.setParams({ wallet: wallet }); this.state = { isLoading: true, isTransactionsLoading: false, wallet: wallet, dataSource: wallet.getTransactions(), - walletBalanceUnit: BitcoinUnit.BTC, + walletPreviousPreferredUnit: wallet.getPreferredBalanceUnit(), }; } @@ -175,22 +173,27 @@ export default class WalletTransactions extends Component { } changeWalletBalanceUnit() { - if (this.state.walletBalanceUnit === undefined || this.state.walletBalanceUnit === BitcoinUnit.BTC) { - // this.setState({ walletBalanceUnit: BitcoinUnit.MBTC }); - this.setState({ walletBalanceUnit: BitcoinUnit.LOCAL_CURRENCY }); - } else if (this.state.walletBalanceUnit === BitcoinUnit.MBTC) { - this.setState({ walletBalanceUnit: BitcoinUnit.BITS }); - } else if (this.state.walletBalanceUnit === BitcoinUnit.BITS) { - this.setState({ walletBalanceUnit: BitcoinUnit.SATOSHIS }); - } else if (this.state.walletBalanceUnit === BitcoinUnit.SATOSHIS) { - this.setState({ walletBalanceUnit: BitcoinUnit.BTC }); - } else if (this.state.walletBalanceUnit === BitcoinUnit.LOCAL_CURRENCY) { - this.setState({ walletBalanceUnit: BitcoinUnit.MBTC }); + let walletPreviousPreferredUnit = this.state.wallet.getPreferredBalanceUnit(); + const wallet = this.state.wallet; + if (walletPreviousPreferredUnit === BitcoinUnit.BTC) { + wallet.preferredBalanceUnit = BitcoinUnit.SATS; + walletPreviousPreferredUnit = BitcoinUnit.BTC; + } else if (walletPreviousPreferredUnit === BitcoinUnit.SATS) { + wallet.preferredBalanceUnit = BitcoinUnit.LOCAL_CURRENCY; + walletPreviousPreferredUnit = BitcoinUnit.SATS; + } else if (walletPreviousPreferredUnit === BitcoinUnit.LOCAL_CURRENCY) { + wallet.preferredBalanceUnit = BitcoinUnit.BTC; + walletPreviousPreferredUnit = BitcoinUnit.BTC; + } else { + wallet.preferredBalanceUnit = BitcoinUnit.BTC; + walletPreviousPreferredUnit = BitcoinUnit.BTC; } + + this.setState({ wallet: wallet, walletPreviousPreferredUnit: walletPreviousPreferredUnit }); } renderWalletHeader = () => { - const gradients = this.props.navigation.getParam('gradients'); + const gradients = this.props.navigation.getParam('gradients') || ['#65ceef', '#68bbe1']; return ( - {loc.formatBalance(this.state.wallet.getBalance(), this.state.walletBalanceUnit)} + {loc.formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit()).toString()} @@ -285,6 +288,10 @@ export default class WalletTransactions extends Component { ); }; + async onWillBlur() { + await BlueApp.saveToDisk(); + } + render() { const { navigate } = this.props.navigation; return ( @@ -293,6 +300,7 @@ export default class WalletTransactions extends Component { onWillFocus={() => { this.refreshFunction(); }} + onWillBlur={() => this.onWillBlur()} /> {this.renderWalletHeader()} @@ -440,11 +448,19 @@ export default class WalletTransactions extends Component { containerStyle: { marginTop: 0 }, }} hideChevron - rightTitle={new BigNumber((rowData.item.value && rowData.item.value) || 0).dividedBy(100000000).toString()} + 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 ? BlueApp.settings.foregroundColor : '#37c0a1', + color: + rowData.item.value / 100000000 < 0 || rowData.item.type === 'paid_invoice' + ? BlueApp.settings.foregroundColor + : '#37c0a1', }} /> );