Browse Source

Merge pull request #171 from BlueWallet/balanceUnit

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)
localNotifications
Igor Korsakov 6 years ago
committed by GitHub
parent
commit
ee8fc835a1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 61
      App.js
  2. 21
      BlueComponents.js
  3. 14
      MainBottomTabs.js
  4. 6
      android/app/src/main/AndroidManifest.xml
  5. 4
      class/abstract-hd-wallet.js
  6. 12
      class/abstract-wallet.js
  7. 1
      class/app-storage.js
  8. 2
      class/hd-segwit-p2sh-wallet.js
  9. 2
      class/legacy-wallet.js
  10. 2
      class/lightning-custodian-wallet.js
  11. 57
      currency.js
  12. 6
      index.js
  13. 2
      ios/BlueWallet.xcodeproj/project.pbxproj
  14. 8
      ios/BlueWallet/AppDelegate.m
  15. 18
      ios/BlueWallet/Info.plist
  16. 2
      loc/en.js
  17. 2
      loc/es.js
  18. 76
      loc/index.js
  19. 2
      loc/pt_BR.js
  20. 2
      loc/pt_PT.js
  21. 2
      loc/ru.js
  22. 2
      loc/ua.js
  23. 3
      models/bitcoinUnits.js
  24. 4
      models/fiatUnit.js
  25. 171
      package-lock.json
  26. 17
      package.json
  27. 30
      screen/lnd/scanLndInvoice.js
  28. 461
      screen/send/details.js
  29. 89
      screen/settings/currency.js
  30. 3
      screen/settings/settings.js
  31. 12
      screen/transactions/details.js
  32. 2
      screen/wallets/add.js
  33. 8
      screen/wallets/details.js
  34. 8
      screen/wallets/import.js
  35. 14
      screen/wallets/list.js
  36. 2
      screen/wallets/scanQrWif.js
  37. 190
      screen/wallets/selectWallet.js
  38. 62
      screen/wallets/transactions.js

61
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 (
<MainBottomTabs
ref={nav => {
this.navigator = nav;
}}
/>
);
}
}

21
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 (
<FormInput
{...this.props}
<TextInput
multiline
underlineColorAndroid="transparent"
numberOfLines={4}
inputStyle={{
width: width - 40,
color: BlueApp.settings.foregroundColor,
height: 120,
// fontSize: (isIpad && 10) || ((is.iphone8() && 12) || 14),
}}
containerStyle={{
style={{
marginTop: 5,
marginHorizontal: 20,
borderColor: '#d2d2d2',
borderBottomColor: '#d2d2d2',
borderWidth: 0.5,
borderBottomWidth: 0.5,
backgroundColor: '#f5f5f5',
height: 200,
color: BlueApp.settings.foregroundColor,
}}
autoCorrect={false}
autoCapitalize="none"
spellCheck={false}
{...this.props}
/>
);
}
@ -1047,7 +1048,7 @@ export class WalletsCarousel extends Component {
color: '#fff',
}}
>
{loc.formatBalance(item.getBalance())}
{loc.formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit())}
</Text>
<Text style={{ backgroundColor: 'transparent' }} />
<Text

14
MainBottomTabs.js

@ -4,6 +4,7 @@ import Settings from './screen/settings/settings';
import About from './screen/settings/about';
import Selftest from './screen/selftest';
import Language from './screen/settings/language';
import Currency from './screen/settings/currency';
import EncryptStorage from './screen/settings/encryptStorage';
import PlausibleDeniability from './screen/plausibledeniability';
import LightningSettings from './screen/settings/lightningSettings';
@ -16,6 +17,7 @@ import WalletExport from './screen/wallets/export';
import BuyBitcoin from './screen/wallets/buyBitcoin';
import scanQrWif from './screen/wallets/scanQrWif';
import ReorderWallets from './screen/wallets/reorderWallets';
import SelectWallet from './screen/wallets/selectWallet';
import details from './screen/transactions/details';
import rbf from './screen/transactions/RBF';
@ -71,6 +73,9 @@ const WalletsStackNavigator = createStackNavigator(
headerTintColor: '#0c2550',
},
},
Currency: {
screen: Currency,
},
About: {
screen: About,
path: 'About',
@ -118,6 +123,9 @@ const CreateTransactionStackNavigator = createStackNavigator({
Success: {
screen: Success,
},
SelectWallet: {
screen: SelectWallet,
},
});
const MainBottomTabs = createStackNavigator(
@ -177,6 +185,12 @@ const MainBottomTabs = createStackNavigator(
header: null,
},
},
// Select Wallet. Mostly for deeplinking
SelectWallet: {
screen: SelectWallet,
},
},
{
mode: 'modal',

6
android/app/src/main/AndroidManifest.xml

@ -20,6 +20,12 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bitcoin" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>

4
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) {

12
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;
}

1
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.<AbstractWallet>} */

2
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;
}

2
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 {

2
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;
}
/**

57
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;

6
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 ? <WalletMigrate onComplete={() => this.setIsMigratingData()} /> : <MainBottomTabs />;
return this.state.isMigratingData ? <WalletMigrate onComplete={() => this.setIsMigratingData()} /> : <App />;
}
}

2
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)",

8
ios/BlueWallet/AppDelegate.m

@ -7,6 +7,7 @@
#import "AppDelegate.h"
#import <React/RCTLinkingManager.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#if __has_include(<React/RNSentry.h>)
@ -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

18
ios/BlueWallet/Info.plist

@ -17,11 +17,25 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.3.0</string>
<string>3.3.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bitcoin</string>
<string>lightning</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>151</string>
<string>168</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>

2
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',

2
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',

76
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;

2
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',

2
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',

2
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',

2
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: 'чи',

3
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',
});

4
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: '€' },
});

171
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"

17
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",

30
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 (
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
@ -162,13 +182,7 @@ export default class ScanLndInvoice extends React.Component {
}}
>
<TextInput
onChangeText={text => {
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,
}),
}),

461
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 (
<Modal
@ -397,6 +444,33 @@ export default class SendDetails extends Component {
);
};
renderWalletSelectionButton = () => {
return (
<View style={{ marginBottom: 24, alignItems: 'center' }}>
{!this.state.isLoading && (
<TouchableOpacity
style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 16 }}
onPress={() => this.props.navigation.navigate('SelectWallet', { onWalletSelect: this.onWalletSelect })}
>
<Text style={{ color: '#9aa0aa', fontSize: 14, paddingHorizontal: 16, alignSelf: 'center' }}>
{loc.wallets.select_wallet.toLowerCase()}
</Text>
<Icon name="angle-right" size={22} type="font-awesome" color="#9aa0aa" />
</TouchableOpacity>
)}
<View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 4 }}>
<Text style={{ color: '#0c2550', fontSize: 14 }}>{this.state.fromWallet.getLabel()}</Text>
<Text style={{ color: '#0c2550', fontSize: 14, fontWeight: '600', marginLeft: 8, marginRight: 4 }}>
{this.state.fromWallet.getBalance()}
</Text>
<Text style={{ color: '#0c2550', fontSize: 11, fontWeight: '600', textAlignVertical: 'bottom', marginTop: 2 }}>
{BitcoinUnit.BTC}
</Text>
</View>
</View>
);
};
render() {
if (!this.state.fromWallet.getAddress) {
return (
@ -408,159 +482,145 @@ export default class SendDetails extends Component {
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<View style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
<KeyboardAvoidingView behavior="position">
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 16, paddingBottom: 16 }}>
<TextInput
keyboardType="numeric"
onChangeText={text => 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',
}}
/>
<Text
style={{
color: '#0f5cc0',
fontSize: 16,
marginHorizontal: 4,
paddingBottom: 6,
fontWeight: '600',
alignSelf: 'flex-end',
}}
>
{' ' + BitcoinUnit.BTC}
</Text>
</View>
<View style={{ alignItems: 'center', marginBottom: 22, marginTop: 4 }}>
<Text style={{ fontSize: 18, color: '#d4d4d4', fontWeight: '600' }}>
{currency.satoshiToLocalCurrency(loc.formatBalanceWithoutSuffix(this.state.amount || 0, BitcoinUnit.SATOSHIS))}
</Text>
</View>
<View
style={{
flexDirection: 'row',
borderColor: '#d2d2d2',
borderBottomColor: '#d2d2d2',
borderWidth: 1.0,
borderBottomWidth: 0.5,
backgroundColor: '#f5f5f5',
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
}}
>
<TextInput
onChangeText={text => {
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}
/>
<TouchableOpacity
disabled={this.state.isLoading}
onPress={() => this.props.navigation.navigate('ScanQrAddress')}
<View style={{ flex: 1, justifyContent: 'space-between' }}>
<View style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
<KeyboardAvoidingView behavior="position">
<View style={{ flexDirection: 'row', justifyContent: 'center', paddingTop: 16, paddingBottom: 16 }}>
<TextInput
keyboardType="numeric"
onChangeText={text => 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',
}}
/>
<Text
style={{
color: '#0f5cc0',
fontSize: 16,
marginHorizontal: 4,
paddingBottom: 6,
fontWeight: '600',
alignSelf: 'flex-end',
}}
>
{' ' + BitcoinUnit.BTC}
</Text>
</View>
<View style={{ alignItems: 'center', marginBottom: 22, marginTop: 4 }}>
<Text style={{ fontSize: 18, color: '#d4d4d4', fontWeight: '600' }}>
{loc.formatBalance(this.state.amount || 0, BitcoinUnit.LOCAL_CURRENCY)}
</Text>
</View>
<View
style={{
width: 75,
height: 36,
flexDirection: 'row',
borderColor: '#d2d2d2',
borderBottomColor: '#d2d2d2',
borderWidth: 1.0,
borderBottomWidth: 0.5,
backgroundColor: '#f5f5f5',
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: '#bebebe',
marginVertical: 8,
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
}}
>
<Icon name="qrcode" size={22} type="font-awesome" color="#FFFFFF" />
<Text style={{ color: '#FFFFFF' }}>{loc.send.details.scan}</Text>
</TouchableOpacity>
</View>
<View
hide={!this.state.showMemoRow}
style={{
flexDirection: 'row',
borderColor: '#d2d2d2',
borderBottomColor: '#d2d2d2',
borderWidth: 1.0,
borderBottomWidth: 0.5,
backgroundColor: '#f5f5f5',
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
}}
>
<TextInput
onChangeText={text => 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}
/>
</View>
<TouchableOpacity
onPress={() => this.setState({ isFeeSelectionModalVisible: true })}
disabled={this.state.isLoading}
style={{ flexDirection: 'row', marginHorizontal: 20, justifyContent: 'space-between', alignItems: 'center' }}
>
<Text style={{ color: '#81868e', fontSize: 14 }}>Fee</Text>
<TextInput
onChangeText={text => {
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}
/>
<TouchableOpacity
disabled={this.state.isLoading}
onPress={() => 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,
}}
>
<Icon name="qrcode" size={22} type="font-awesome" color="#FFFFFF" />
<Text style={{ color: '#FFFFFF' }}>{loc.send.details.scan}</Text>
</TouchableOpacity>
</View>
<View
hide={!this.state.showMemoRow}
style={{
backgroundColor: '#d2f8d6',
minWidth: 40,
height: 25,
borderRadius: 4,
justifyContent: 'space-between',
flexDirection: 'row',
borderColor: '#d2d2d2',
borderBottomColor: '#d2d2d2',
borderWidth: 1.0,
borderBottomWidth: 0.5,
backgroundColor: '#f5f5f5',
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
paddingHorizontal: 10,
marginVertical: 8,
borderRadius: 4,
}}
>
<Text style={{ color: '#37c0a1', marginBottom: 0, marginRight: 4, textAlign: 'right' }}>{this.state.fee}</Text>
<Text style={{ color: '#37c0a1', paddingRight: 4, textAlign: 'left' }}>sat/b</Text>
<TextInput
onChangeText={text => 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}
/>
</View>
</TouchableOpacity>
{this.renderCreateButton()}
{this.renderFeeSelectionModal()}
</KeyboardAvoidingView>
<TouchableOpacity
onPress={() => this.setState({ isFeeSelectionModalVisible: true })}
disabled={this.state.isLoading}
style={{ flexDirection: 'row', marginHorizontal: 20, justifyContent: 'space-between', alignItems: 'center' }}
>
<Text style={{ color: '#81868e', fontSize: 14 }}>Fee</Text>
<View
style={{
backgroundColor: '#d2f8d6',
minWidth: 40,
height: 25,
borderRadius: 4,
justifyContent: 'space-between',
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 10,
}}
>
<Text style={{ color: '#37c0a1', marginBottom: 0, marginRight: 4, textAlign: 'right' }}>{this.state.fee}</Text>
<Text style={{ color: '#37c0a1', paddingRight: 4, textAlign: 'left' }}>sat/b</Text>
</View>
</TouchableOpacity>
{this.renderCreateButton()}
{this.renderFeeSelectionModal()}
</KeyboardAvoidingView>
</View>
{this.renderWalletSelectionButton()}
</View>
</TouchableWithoutFeedback>
);
@ -605,6 +665,7 @@ SendDetails.propTypes = {
satoshiPerByte: PropTypes.string,
fromSecret: PropTypes.fromSecret,
memo: PropTypes.string,
uri: PropTypes.string,
}),
}),
}),

89
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 (
<TouchableOpacity
onPress={() => {
this.setState({ isSavingNewPreferredCurrency: true, selectedCurrency: item }, async () => {
await AsyncStorage.setItem(AppStorage.PREFERREDCURRENCY, JSON.stringify(item));
await currency.startUpdater(true);
this.setState({ isSavingNewPreferredCurrency: false });
});
}}
>
<BlueListItem
title={item.symbol + ' ' + item.formatterValue}
{...(this.state.selectedCurrency.formatterValue === item.formatterValue
? {
rightIcon: this.state.selectedNewCurrency ? (
<ActivityIndicator />
) : (
<Icon name="check" type="font-awesome" color="#0c2550" />
),
}
: { hideChevron: true })}
/>
</TouchableOpacity>
);
};
render() {
if (this.state.selectedCurrency !== null && this.state.selectedCurrency !== undefined) {
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<FlatList
style={{ flex: 1 }}
keyExtractor={(_item, index) => `${index}`}
data={this.state.data}
extraData={this.state.data}
renderItem={this.renderItem}
/>
</SafeBlueArea>
);
}
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator />
</View>
);
}
}
Currency.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
goBack: PropTypes.func,
}),
};

3
screen/settings/settings.js

@ -44,6 +44,9 @@ export default class Settings extends Component {
<TouchableOpacity onPress={() => this.props.navigation.navigate('Language')}>
<BlueListItem title={loc.settings.language} />
</TouchableOpacity>
<TouchableOpacity onPress={() => this.props.navigation.navigate('Currency')}>
<BlueListItem title={loc.settings.currency} />
</TouchableOpacity>
<TouchableOpacity onPress={() => this.props.navigation.navigate('About')}>
<BlueListItem title={loc.settings.about} />
</TouchableOpacity>

12
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 {
</React.Fragment>
)}
{this.state.tx.hasOwnProperty('fee') && (
<React.Fragment>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>{loc.send.create.fee}</BlueText>
<BlueText style={{ marginBottom: 26, color: 'grey' }}>
{loc.formatBalance(this.state.tx.fee, BitcoinUnit.SATS, BitcoinUnit.BTC)}
</BlueText>
</React.Fragment>
)}
{this.state.tx.hasOwnProperty('hash') && (
<React.Fragment>
<View style={{ flex: 1, flexDirection: 'row', marginBottom: 4, justifyContent: 'space-between' }}>
@ -160,6 +169,7 @@ export default class TransactionsDetails extends Component {
</TouchableOpacity>
</React.Fragment>
)}
{this.state.tx.hasOwnProperty('received') && (
<React.Fragment>
<BlueText style={{ fontSize: 16, fontWeight: '500', marginBottom: 4 }}>Received</BlueText>

2
screen/wallets/add.js

@ -95,7 +95,7 @@ export default class WalletsAdd extends Component {
<TextInput
value={this.state.label}
placeholderTextColor="#81868e"
placeholder={loc.wallets.add.label_new_segwit}
placeholder={this.state.activeBitcoin ? loc.wallets.add.label_new_segwit : loc.wallets.add.label_new_lightning}
onChangeText={text => {
this.setLabel(text);
}}

8
screen/wallets/details.js

@ -16,7 +16,7 @@ export default class WalletDetails extends Component {
title: loc.wallets.details.title,
headerRight: (
<TouchableOpacity
style={{ marginHorizontal: 16 }}
style={{ marginHorizontal: 16, height: 40, width: 40, justifyContent: 'center', alignItems: 'center' }}
onPress={() => {
navigation.getParam('saveAction')();
}}
@ -73,7 +73,6 @@ export default class WalletDetails extends Component {
</View>
);
}
return (
<SafeBlueArea style={{ flex: 1 }}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
@ -128,11 +127,6 @@ export default class WalletDetails extends Component {
<View>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'cloud-upload',
type: 'octicon',
color: BlueApp.settings.buttonTextColor,
}}
onPress={() =>
this.props.navigation.navigate('WalletExport', {
address: this.state.wallet.getAddress(),

8
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 {
<BlueSpacing20 />
<BlueFormMultiInput
value={this.state.label}
placeholder={''}
placeholder=""
onChangeText={text => {
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,
}),
};

14
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: (
<TouchableOpacity
style={{ marginHorizontal: 16, width: 40, height: 40, justifyContent: 'flex-end', alignContent: 'flex-end' }}
style={{ marginHorizontal: 16, width: 40, height: 40, justifyContent: 'center', alignItems: 'flex-end' }}
onPress={() => navigation.navigate('Settings')}
>
<Icon name="kebab-horizontal" size={22} type="octicon" color={BlueApp.settings.foregroundColor} />
@ -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',
}}
/>
);

2
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';

190
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 (
<TouchableOpacity
onPress={() => {
ReactNativeHapticFeedback.trigger('selection', false);
this.props.navigation.getParam('onWalletSelect')(item);
}}
>
<View
shadowOpacity={40 / 100}
shadowOffset={{ width: 0, height: 0 }}
shadowRadius={5}
style={{ backgroundColor: 'transparent', padding: 10, marginVertical: 17 }}
>
<LinearGradient
shadowColor="#000000"
colors={[gradient1, gradient2]}
style={{
padding: 15,
borderRadius: 10,
minHeight: 164,
elevation: 5,
}}
>
<Image
source={
(new LightningCustodianWallet().type === item.type && require('../../img/lnd-shape.png')) ||
require('../../img/btc-shape.png')
}
style={{
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
}}
/>
<Text style={{ backgroundColor: 'transparent' }} />
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontSize: 19,
color: '#fff',
}}
>
{item.getLabel()}
</Text>
<Text
numberOfLines={1}
adjustsFontSizeToFit
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: '#fff',
}}
>
{loc.formatBalance(item.getBalance())}
</Text>
<Text style={{ backgroundColor: 'transparent' }} />
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontSize: 13,
color: '#fff',
}}
>
{loc.wallets.list.latest_transaction}
</Text>
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 16,
color: '#fff',
}}
>
{loc.transactionTimeToReadable(item.getLatestTransactionTime())}
</Text>
</LinearGradient>
</View>
</TouchableOpacity>
);
};
render() {
if (this.state.isLoading || this.state.data.length <= 0) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<ActivityIndicator />
</View>
);
}
return (
<SafeBlueArea>
<FlatList
style={{ flex: 1 }}
extraData={this.state.data}
data={this.state.data}
renderItem={this._renderItem}
keyExtractor={(_item, index) => `${index}`}
/>
</SafeBlueArea>
);
}
}
SelectWallet.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
setParams: PropTypes.func,
dismiss: PropTypes.func,
getParam: PropTypes.func,
}),
};

62
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: (
<TouchableOpacity
style={{ marginHorizontal: 8 }}
style={{ marginHorizontal: 8, minWidth: 150 }}
onPress={() =>
navigation.navigate('WalletDetails', {
address: navigation.state.params.wallet.getAddress(),
@ -38,11 +37,11 @@ export default class WalletTransactions extends Component {
})
}
>
<Text style={{ color: '#fff', fontSize: 20, fontWeight: '500' }}>{loc.wallets.options}</Text>
<Text style={{ color: '#fff', fontSize: 20, fontWeight: '500', textAlign: 'right' }}>{loc.wallets.options}</Text>
</TouchableOpacity>
),
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 (
<LinearGradient colors={[gradients[0], gradients[1]]} style={{ padding: 15, minHeight: 164 }}>
<Image
@ -229,7 +232,7 @@ export default class WalletTransactions extends Component {
color: '#fff',
}}
>
{loc.formatBalance(this.state.wallet.getBalance(), this.state.walletBalanceUnit)}
{loc.formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit()).toString()}
</Text>
</TouchableOpacity>
<Text style={{ backgroundColor: 'transparent' }} />
@ -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()}
<View style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
@ -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',
}}
/>
);

Loading…
Cancel
Save