Merge pull request #16 from sansegkh/master

merged from master
This commit is contained in:
San Segkhoonthod 2019-02-23 10:19:29 +07:00 committed by GitHub
commit 9e2421f73f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 3916 additions and 4144 deletions

View file

@ -9,7 +9,7 @@ import MockStorage from './MockStorage';
import { FiatUnit } from './models/fiatUnit'; import { FiatUnit } from './models/fiatUnit';
global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment global.crypto = require('crypto'); // shall be used by tests under nodejs CLI, but not in RN environment
let assert = require('assert'); let assert = require('assert');
jest.mock('react-native-custom-qr-codes', () => 'Video'); jest.mock('react-native-qrcode-svg', () => 'Video');
const AsyncStorage = new MockStorage(); const AsyncStorage = new MockStorage();
jest.setMock('AsyncStorage', AsyncStorage); jest.setMock('AsyncStorage', AsyncStorage);
jest.useFakeTimers(); jest.useFakeTimers();

View file

@ -45,7 +45,7 @@ if (aspectRatio > 1.6) {
export class BlueButton extends Component { export class BlueButton extends Component {
render() { render() {
let backgroundColor = '#ccddf9'; let backgroundColor = this.props.backgroundColor ? this.props.backgroundColor : '#ccddf9';
let fontColor = '#0c2550'; let fontColor = '#0c2550';
if (this.props.hasOwnProperty('disabled') && this.props.disabled === true) { if (this.props.hasOwnProperty('disabled') && this.props.disabled === true) {
backgroundColor = '#eef0f4'; backgroundColor = '#eef0f4';
@ -177,7 +177,14 @@ export const BlueNavigationStyle = (navigation, withNavigationCloseButton = fals
headerRight: withNavigationCloseButton ? ( headerRight: withNavigationCloseButton ? (
<TouchableOpacity <TouchableOpacity
style={{ width: 40, height: 40, padding: 14 }} style={{ width: 40, height: 40, padding: 14 }}
onPress={customCloseButtonFunction === undefined ? () => navigation.goBack(null) : customCloseButtonFunction} onPress={
customCloseButtonFunction === undefined
? () => {
Keyboard.dismiss();
navigation.goBack(null);
}
: customCloseButtonFunction
}
> >
<Image style={{ alignSelf: 'center' }} source={require('./img/close.png')} /> <Image style={{ alignSelf: 'center' }} source={require('./img/close.png')} />
</TouchableOpacity> </TouchableOpacity>
@ -566,6 +573,29 @@ export class BlueUseAllFundsButton extends Component {
} }
} }
export class BlueDismissKeyboardInputAccessory extends Component {
static InputAccessoryViewID = 'BlueDismissKeyboardInputAccessory';
render() {
return Platform.OS !== 'ios' ? null : (
<InputAccessoryView nativeID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}>
<View
style={{
backgroundColor: '#eef0f4',
height: 44,
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
}}
>
<BlueButtonLink title="Done" onPress={Keyboard.dismiss} />
</View>
</InputAccessoryView>
);
}
}
export class BlueLoading extends Component { export class BlueLoading extends Component {
render() { render() {
return ( return (
@ -1539,11 +1569,13 @@ export class BlueAddressInput extends Component {
onChangeText: PropTypes.func, onChangeText: PropTypes.func,
onBarScanned: PropTypes.func, onBarScanned: PropTypes.func,
address: PropTypes.string, address: PropTypes.string,
placeholder: PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
isLoading: false, isLoading: false,
address: '', address: '',
placeholder: loc.send.details.address,
}; };
render() { render() {
@ -1568,16 +1600,18 @@ export class BlueAddressInput extends Component {
onChangeText={text => { onChangeText={text => {
this.props.onChangeText(text); this.props.onChangeText(text);
}} }}
placeholder={loc.send.details.address} placeholder={this.props.placeholder}
numberOfLines={1} numberOfLines={1}
value={this.props.address} value={this.props.address}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
editable={!this.props.isLoading} editable={!this.props.isLoading}
onSubmitEditing={Keyboard.dismiss} onSubmitEditing={Keyboard.dismiss}
{...this.props}
/> />
<TouchableOpacity <TouchableOpacity
disabled={this.props.isLoading} disabled={this.props.isLoading}
onPress={() => { onPress={() => {
Keyboard.dismiss();
ImagePicker.showImagePicker( ImagePicker.showImagePicker(
{ {
title: null, title: null,

View file

@ -102,7 +102,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 1
versionName "3.7.2" versionName "3.8.0"
ndk { ndk {
abiFilters "armeabi-v7a", "x86" abiFilters "armeabi-v7a", "x86"
} }

View file

@ -20,6 +20,13 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </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" />
<data android:scheme="lightning" />
</intent-filter>
</activity> </activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application> </application>

View file

@ -394,9 +394,6 @@ export class AbstractHDWallet extends LegacyWallet {
} }
this.next_free_address_index = await binarySearchIterationForExternalAddress(100); this.next_free_address_index = await binarySearchIterationForExternalAddress(100);
this.balance = 0;
this.unconfirmed_balance = 0;
this.usedAddresses = []; this.usedAddresses = [];
// generating all involved addresses: // generating all involved addresses:

View file

@ -262,6 +262,7 @@ export class AppStorage {
* @return {Promise.<void>} * @return {Promise.<void>}
*/ */
async fetchWalletBalances(index) { async fetchWalletBalances(index) {
console.log('fetchWalletBalances for wallet#', index);
if (index || index === 0) { if (index || index === 0) {
let c = 0; let c = 0;
for (let wallet of this.wallets) { for (let wallet of this.wallets) {
@ -287,6 +288,7 @@ export class AppStorage {
* @return {Promise.<void>} * @return {Promise.<void>}
*/ */
async fetchWalletTransactions(index) { async fetchWalletTransactions(index) {
console.log('fetchWalletTransactions for wallet#', index);
if (index || index === 0) { if (index || index === 0) {
let c = 0; let c = 0;
for (let wallet of this.wallets) { for (let wallet of this.wallets) {
@ -325,10 +327,11 @@ export class AppStorage {
* Getter for all transactions in all wallets. * Getter for all transactions in all wallets.
* But if index is provided - only for wallet with corresponding index * But if index is provided - only for wallet with corresponding index
* *
* @param index {Integer} Wallet index in this.wallets. Empty for all wallets. * @param index {Integer|null} Wallet index in this.wallets. Empty (or null) for all wallets.
* @param limit {Integer} How many txs return, starting from the earliest. Default: all of them.
* @return {Array} * @return {Array}
*/ */
getTransactions(index) { getTransactions(index, limit = Infinity) {
if (index || index === 0) { if (index || index === 0) {
let txs = []; let txs = [];
let c = 0; let c = 0;
@ -353,9 +356,11 @@ export class AppStorage {
t.sort_ts = +new Date(t.received); t.sort_ts = +new Date(t.received);
} }
return txs.sort(function(a, b) { return txs
return b.sort_ts - a.sort_ts; .sort(function(a, b) {
}); return b.sort_ts - a.sort_ts;
})
.slice(0, limit);
} }
/** /**

View file

@ -40,8 +40,7 @@ export class LightningCustodianWallet extends LegacyWallet {
} }
allowSend() { allowSend() {
console.log(this.getBalance(), this.getBalance() > 0); return true;
return this.getBalance() > 0;
} }
getAddress() { getAddress() {
@ -49,13 +48,11 @@ export class LightningCustodianWallet extends LegacyWallet {
} }
timeToRefreshBalance() { timeToRefreshBalance() {
// only manual refresh for now return (+new Date() - this._lastBalanceFetch) / 1000 > 3600; // 1hr
return false;
} }
timeToRefreshTransaction() { timeToRefreshTransaction() {
// only manual refresh for now return (+new Date() - this._lastTxFetch) / 1000 > 3600; // 1hr
return false;
} }
static fromJson(param) { static fromJson(param) {
@ -112,6 +109,17 @@ export class LightningCustodianWallet extends LegacyWallet {
Authorization: 'Bearer' + ' ' + this.access_token, Authorization: 'Bearer' + ' ' + this.access_token,
}, },
}); });
if (response.originalResponse && typeof response.originalResponse === 'string') {
try {
response.originalResponse = JSON.parse(response.originalResponse);
} catch (_) {}
}
if (response.originalResponse && response.originalResponse.status && response.originalResponse.status === 503) {
throw new Error('Payment is in transit');
}
let json = response.body; let json = response.body;
if (typeof json === 'undefined') { if (typeof json === 'undefined') {
throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.originalResponse)); throw new Error('API failure: ' + response.err + ' ' + JSON.stringify(response.originalResponse));
@ -434,6 +442,7 @@ export class LightningCustodianWallet extends LegacyWallet {
throw new Error('API unexpected response: ' + JSON.stringify(response.body)); throw new Error('API unexpected response: ' + JSON.stringify(response.body));
} }
this._lastTxFetch = +new Date();
this.transactions_raw = json; this.transactions_raw = json;
} }

View file

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.7.2</string> <string>3.8.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>

View file

@ -1,29 +1,17 @@
v3.5.7 v3.7.2
------ ======
* FIX: Source wallet not found #239 * FIX: status bar disappears #311
* FIX: Changed the way currencies are shown in order to avoid indentation issue. FIX: Lint * FIX: faulty back button on viewLndInvoice screen
* FIX: Reordered the language list by alphabetical order * FIX: lnd - create invoice - cant tap bottom half of button #303
* FIX: Localization fixes - Fix some dutch translation * FIX: Impossible to scan bech32 QR invoice with a specific amount (closes #296)
* FIX: Do not dismiss modal on error, allow for network retry. * REF: better lightning error reporting
* FIX: Use DayJS locale for formatting dates * FIX: not redering QR code #302
* FIX: too small QR on ln receive invoice (closes #235) * ADD: Indonesian Translation
* FIX: send amount in satoshi wasnt with minus in tx list * FIX: better LN wallet auto-refresh strategy
* FIX: Date/time specifications #186 * FIX: Dismiss keyboard when pressing return
* ADD: Use platform's rating UI in-app * FIX: HD wallet balance refresh
* FIX: limit number of userinvoices feetched per polling request * FIX: no wallet refresh upon startup (faster to start app)
* REF: less remote fetches * FIX: Dismiss keyboard when pressing return
* FIX: sometimes export QR is unreadable * REF: HD wallet getbalance improvements
* REF: HD wallets address derivation * ADD: NZD as a currency
* ADD: Added 日本語 in settings
* FIX: When having multiple wallets, I was unable to see my transactions description from the main screen
* FIX: Writing numbers standards #194
* FIX: Remove Lightning wallet creation limitation
* FIX: Can't create a Lightning Wallet 3.5.6 (223) #224
* FIX: zigzag link
* FIX: Lightning balance/fees don't add up (closes #225)
* ADD: THB fiat
* FIX: Thai translation
* FIX: Can't scan Bech32 addresses (closes #222)
* ADD: Croatian language into Settings.
* ADD: HRK Fiat in currency settings

View file

@ -214,6 +214,7 @@ module.exports = {
refill: 'Doplnit', refill: 'Doplnit',
withdraw: 'Vybrat', withdraw: 'Vybrat',
expired: 'Expirováno', expired: 'Expirováno',
placeholder: 'Invoice',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
}, },
}; };

View file

@ -210,6 +210,7 @@ module.exports = {
}, },
lnd: { lnd: {
title: 'Administration', title: 'Administration',
placeholder: 'Invoice',
choose_source_wallet: 'Vælge en wallet', choose_source_wallet: 'Vælge en wallet',
refill_lnd_balance: 'Genopfyld Lightning wallet', refill_lnd_balance: 'Genopfyld Lightning wallet',
refill: 'Genopfyld', refill: 'Genopfyld',

View file

@ -217,6 +217,7 @@ module.exports = {
refill_lnd_balance: 'Lade deine Lightning Wallet auf', refill_lnd_balance: 'Lade deine Lightning Wallet auf',
refill: 'Aufladen', refill: 'Aufladen',
withdraw: 'Abheben', withdraw: 'Abheben',
placeholder: 'Invoice',
sameWalletAsInvoiceError: sameWalletAsInvoiceError:
'Du kannst nicht die Rechnung mit der Wallet begleichen, die du für die Erstellung dieser Rechnung verwendet hast.', 'Du kannst nicht die Rechnung mit der Wallet begleichen, die du für die Erstellung dieser Rechnung verwendet hast.',
}, },

View file

@ -215,6 +215,7 @@ module.exports = {
refill: 'Refill', refill: 'Refill',
withdraw: 'Withdraw', withdraw: 'Withdraw',
expired: 'Expired', expired: 'Expired',
placeholder: 'Invoice',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
}, },
}; };

View file

@ -217,6 +217,7 @@ module.exports = {
refill_lnd_balance: 'Rellenar el balance de la billetera Lightning', refill_lnd_balance: 'Rellenar el balance de la billetera Lightning',
refill: 'Rellenar', refill: 'Rellenar',
withdraw: 'Retirar', withdraw: 'Retirar',
placeholder: 'Invoice',
expired: 'Expirado', expired: 'Expirado',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',
}, },

View file

@ -216,6 +216,7 @@ module.exports = {
refill_lnd_balance: 'Déposer des fonds dans votre portfeuille Lightning', refill_lnd_balance: 'Déposer des fonds dans votre portfeuille Lightning',
refill: 'Déposer des fonds', refill: 'Déposer des fonds',
withdraw: 'Retirer des fonds', withdraw: 'Retirer des fonds',
placeholder: 'Invoice',
expired: 'Expiré', expired: 'Expiré',
sameWalletAsInvoiceError: 'Vous ne pouvez pas payer une facture avec le même portefeuille utilisé pour la créer.', sameWalletAsInvoiceError: 'Vous ne pouvez pas payer une facture avec le même portefeuille utilisé pour la créer.',
}, },

View file

@ -211,6 +211,7 @@ module.exports = {
refill_lnd_balance: 'Dopuni Lightning volet saldo', refill_lnd_balance: 'Dopuni Lightning volet saldo',
refill: 'Dopuni', refill: 'Dopuni',
withdraw: 'Isprazni', withdraw: 'Isprazni',
placeholder: 'Invoice',
expired: 'Isteklo', expired: 'Isteklo',
sameWalletAsInvoiceError: 'Buraz! Ne možeš platiti račun s istim voletom s kojim si račun stvorio, ono.', sameWalletAsInvoiceError: 'Buraz! Ne možeš platiti račun s istim voletom s kojim si račun stvorio, ono.',
}, },

220
loc/id_ID.js Normal file
View file

@ -0,0 +1,220 @@
module.exports = {
_: {
storage_is_encrypted: 'Penyimpanan dienkripsi. Masukkan kata sandi untuk dekripsi:',
enter_password: 'Masukkan kata sandi',
bad_password: 'kata sandi salah, coba lagi',
never: 'tidak pernah',
continue: 'Lanjutkan',
ok: 'OK',
},
wallets: {
select_wallet: 'Pilih dompet',
options: 'Opsi',
createBitcoinWallet:
'Belum ada dompet bitcoin. Untuk mendanai dompet Lightning, dompet Bitcoin harus dibuat atau diimpor. Yakin ingin melanjutkan?',
list: {
app_name: 'BlueWallet',
title: 'Dompet',
header: 'Sebuah dompet mewakili sepasang kunci rahasia dan sebuah alamat' + 'yang bisa dipilih untuk menerima koin.',
add: 'Tambah dompet',
create_a_wallet: 'Buat dompet',
create_a_wallet1: 'Gratis dan bisa buat',
create_a_wallet2: 'sebanyak yang kamu mau',
latest_transaction: 'transaksi terbaru',
empty_txs1: 'Transaksimu akan muncul di sini,',
empty_txs2: 'saat ini tidak ada transaksi',
tap_here_to_buy: 'Tap di sini untuk membeli bitcoin',
},
reorder: {
title: 'Susun Dompet',
},
add: {
title: 'tambah dompet',
description:
'Kamu bisa membuat dompet atau memindai paper wallet dalam WIF (Wallet Import Format). Bluewallet mendukung dompet Segwit.',
scan: 'Pindai',
create: 'Buat',
label_new_segwit: 'Dompet SegWit baru',
label_new_lightning: 'Dompet Lightning baru',
wallet_name: 'nama dompet',
wallet_type: 'tipe',
or: 'atau',
import_wallet: 'Impor dompet',
imported: 'Diimpor',
coming_soon: 'Akan datang',
lightning: 'Lightning',
bitcoin: 'Bitcoin',
},
details: {
title: 'Dompet',
address: 'Alamat',
type: 'Tipe',
label: 'Label',
destination: 'tujuan',
description: 'deskripsi',
are_you_sure: 'Yakin?',
yes_delete: 'Ya, hapus',
no_cancel: 'Tidak, batalkan',
delete: 'Hapus',
save: 'Simpan',
delete_this_wallet: 'Hapus dompet ini',
export_backup: 'Ekspor / backup',
buy_bitcoin: 'Beli Bitcoin',
show_xpub: 'Tampilkan XPUB dompet',
},
export: {
title: 'ekspor dompet',
},
xpub: {
title: 'XPUB dompet',
copiedToClipboard: 'Disalin ke clipboard.',
},
import: {
title: 'impor',
explanation: 'Ketik kata mnemonic, private key, WIF, atau apapun yang kamu punya. BlueWallet akan mencoba mengimpor dompet kamu.',
imported: 'Diimpor',
error: 'Gagal mengimpor. Pastikan data yang diketik benar.',
success: 'Berhasil',
do_import: 'Impor',
scan_qr: 'atau mau pindai QR code?',
},
scanQrWif: {
go_back: 'Kembali',
cancel: 'Batal',
decoding: 'Membaca...',
input_password: 'Masukkan kata sandi',
password_explain: 'Ini adalah private key terenkripsi BIP38',
bad_password: 'kata sandi salah',
wallet_already_exists: 'Dompet sudah ada',
bad_wif: 'WIF salah',
imported_wif: 'WIF diimpor ',
with_address: ' dengan alamat ',
imported_segwit: 'Dompet SegWit diimpor',
imported_legacy: 'Dompet lawas diimpor',
imported_watchonly: 'Alamat tinjauan diimpor',
},
},
transactions: {
list: {
tabBarLabel: 'Transaksi',
title: 'transaksi',
description: 'Daftar transaksi keluar dan masuk dompet',
conf: 'konfirmasi',
},
details: {
title: 'Transaksi',
from: 'Input',
to: 'Output',
copy: 'Salin',
transaction_details: 'Detail transaksi',
show_in_block_explorer: 'Tampilkan di block explorer',
},
},
send: {
header: 'Kirim',
details: {
title: 'buat transaksi',
amount_field_is_not_valid: 'Jumlah tidak valid',
fee_field_is_not_valid: 'Tarif tidak valid',
address_field_is_not_valid: 'Alamat tidak valid',
total_exceeds_balance: 'Jumlah yang dikirim melebihi saldo.',
create_tx_error: 'Kesalahan dalam membuat transaksi. Cek kembali alamat tujuan.',
address: 'alamat',
amount_placeholder: 'jumlah (dalam BTC)',
fee_placeholder: 'Tambahan biaya transaksi (dalam BTC)',
note_placeholder: 'catatan pribadi',
cancel: 'Batalkan',
scan: 'Pindai',
send: 'Kirim',
create: 'Buat',
remaining_balance: 'Sisa saldo',
},
confirm: {
header: 'Konfirmasi',
sendNow: 'Kirim sekarang',
},
success: {
done: 'Selesai',
},
create: {
details: 'Detail',
title: 'buat transaksi',
error: 'Tidak bisa membuat transaksi. Cek alamat atau jumlah transfer.',
go_back: 'Kembali',
this_is_hex: 'Ini adalah hex transaksi, siap untuk disiarkan ke jaringan.',
to: 'Ke',
amount: 'Jumlah',
fee: 'Tarif',
tx_size: 'Ukuran TX',
satoshi_per_byte: 'Satoshi per byte',
memo: 'Memo',
broadcast: 'Siarkan',
not_enough_fee: 'Tarif tidak cukup. Naikkan tarif',
},
},
receive: {
header: 'Terima',
details: {
title: 'Bagikan alamat ini ke pengirim',
share: 'bagikan',
copiedToClipboard: 'Disalin ke clipboard.',
label: 'Deskripsi',
create: 'Buat',
setAmount: 'Terima sejumlah',
},
},
buyBitcoin: {
header: 'Beli bitcoin',
tap_your_address: 'Untuk menyalin, tap alamat:',
copied: 'Disalin ke Clipboard!',
},
settings: {
header: 'setting',
plausible_deniability: 'Plausible deniability...',
storage_not_encrypted: 'Penyimpanan: tidak terenkripsi',
storage_encrypted: 'Penyimpanan: terenkripsi',
password: 'kata sandi',
password_explain: 'Buat kata sandi untuk dekripsi penyimpanan',
retype_password: 'Ulangi kata sandi',
passwords_do_not_match: 'Kata sandi tidak cocok',
encrypt_storage: 'Enkripsi penyimpanan',
lightning_settings: 'Pengaturan Lightning',
lightning_settings_explain:
'Pasang LndHub untuk menghubungkan ke node LND kamu' +
' dan masukkan URL di sini. Biarkan kosong untuk menghubungkan ke LndHub standar (lndhub.io)',
save: 'simpan',
about: 'Tentang',
language: 'Bahasa',
currency: 'Mata Uang',
},
plausibledeniability: {
title: 'Plausible Deniability',
help:
'Under certain circumstances, you might be forced to disclose a ' +
'password. To keep your coins safe, BlueWallet can create another ' +
'encrypted storage, with a different password. Under pressure, ' +
'you can disclose this password to a 3rd party. If entered in ' +
"BlueWallet, it will unlock new 'fake' storage. This will seem " +
'legit to a 3rd party, but will secretly keep your main storage ' +
'with coins safe.',
help2: 'New storage will be fully functional, and you can store some ' + 'minimum amounts there so it looks more believable.',
create_fake_storage: 'Create fake encrypted storage',
go_back: 'Go Back',
create_password: 'Create a password',
create_password_explanation: 'Password for fake storage should not match password for your main storage',
password_should_not_match: 'Password for fake storage should not match password for your main storage',
retype_password: 'Retype password',
passwords_do_not_match: 'Passwords do not match, try again',
success: 'Success',
},
lnd: {
title: 'atur dana',
choose_source_wallet: 'Pilih dompet sumber',
refill_lnd_balance: 'Isi ulang saldo Lightning',
refill: 'Isi ulang',
withdraw: 'Tarik',
placeholder: 'Invoice',
expired: 'Kadaluarsa',
sameWalletAsInvoiceError: 'Kamu tidak bisa membayar invoice dengan dompet yang sama yang dipakai untuk membuat invoice.',
},
};

View file

@ -17,6 +17,9 @@ dayjs.extend(relativeTime);
strings.setLanguage(lang); strings.setLanguage(lang);
let localeForDayJSAvailable = true; let localeForDayJSAvailable = true;
switch (lang) { switch (lang) {
case 'zh':
require('dayjs/locale/zh-cn');
break;
case 'ru': case 'ru':
require('dayjs/locale/ru'); require('dayjs/locale/ru');
break; break;
@ -53,6 +56,9 @@ dayjs.extend(relativeTime);
case 'hr_hr': case 'hr_hr':
require('dayjs/locale/hr'); require('dayjs/locale/hr');
break; break;
case 'id_id':
require('dayjs/locale/id');
break;
default: default:
localeForDayJSAvailable = false; localeForDayJSAvailable = false;
break; break;
@ -81,9 +87,14 @@ dayjs.extend(relativeTime);
locale === 'th-th' || locale === 'th-th' ||
locale === 'da-dk' || locale === 'da-dk' ||
locale === 'nl-nl' || locale === 'nl-nl' ||
locale === 'hr-hr' locale === 'hr-hr' ||
locale === 'id-id' ||
locale === 'zh-cn'
) { ) {
switch (locale) { switch (locale) {
case 'zh-cn':
require('dayjs/locale/zh-cn');
break;
case 'ru': case 'ru':
require('dayjs/locale/ru'); require('dayjs/locale/ru');
break; break;
@ -117,6 +128,9 @@ dayjs.extend(relativeTime);
case 'hr-hr': case 'hr-hr':
require('dayjs/locale/hr'); require('dayjs/locale/hr');
break; break;
case 'id-id':
require('dayjs/locale/id');
break;
default: default:
break; break;
} }
@ -145,6 +159,8 @@ strings = new Localization({
nl_nl: require('./nl_NL.js'), nl_nl: require('./nl_NL.js'),
fr_fr: require('./fr_FR.js'), fr_fr: require('./fr_FR.js'),
hr_hr: require('./hr_HR.js'), hr_hr: require('./hr_HR.js'),
id_id: require('./id_ID.js'),
zh_cn: require('./zh_cn.js'),
}); });
strings.saveLanguage = lang => AsyncStorage.setItem(AppStorage.LANG, lang); strings.saveLanguage = lang => AsyncStorage.setItem(AppStorage.LANG, lang);
@ -156,13 +172,6 @@ strings.transactionTimeToReadable = time => {
return dayjs(time).fromNow(); return dayjs(time).fromNow();
}; };
strings.transactionTimeToReadableToFuture = time => {
if (time === 0) {
return strings._.never;
}
return dayjs(time).toNow();
};
function removeTrailingZeros(value) { function removeTrailingZeros(value) {
value = value.toString(); value = value.toString();

View file

@ -216,6 +216,7 @@ module.exports = {
refill_lnd_balance: 'Lightning ウォレットへ送金', refill_lnd_balance: 'Lightning ウォレットへ送金',
refill: '送金', refill: '送金',
withdraw: '引き出し', withdraw: '引き出し',
placeholder: '入金依頼',
expired: '失効', expired: '失効',
sameWalletAsInvoiceError: '以前作成したウォレットと同じウォレットへの支払いはできません。', sameWalletAsInvoiceError: '以前作成したウォレットと同じウォレットへの支払いはできません。',
}, },

View file

@ -218,6 +218,7 @@ module.exports = {
refill: 'Bijvullen', refill: 'Bijvullen',
withdraw: 'Opvragen', withdraw: 'Opvragen',
expired: 'Verlopen', expired: 'Verlopen',
placeholder: 'Invoice',
sameWalletAsInvoiceError: 'U kunt geen factuur betalen met dezelfde portemonnee die is gebruikt om de factuur te maken.', sameWalletAsInvoiceError: 'U kunt geen factuur betalen met dezelfde portemonnee die is gebruikt om de factuur te maken.',
}, },
}; };

View file

@ -217,6 +217,7 @@ module.exports = {
choose_source_wallet: 'Escolha a carteira de origem', choose_source_wallet: 'Escolha a carteira de origem',
refill_lnd_balance: 'Recarregar a carteira Lightning', refill_lnd_balance: 'Recarregar a carteira Lightning',
refill: 'Recarregar', refill: 'Recarregar',
placeholder: 'Invoice',
withdraw: 'Sacar', withdraw: 'Sacar',
expired: 'Vencido', expired: 'Vencido',
sameWalletAsInvoiceError: 'Você não pode pagar uma fatura com a mesma carteira que a criou.', sameWalletAsInvoiceError: 'Você não pode pagar uma fatura com a mesma carteira que a criou.',

View file

@ -216,6 +216,7 @@ module.exports = {
choose_source_wallet: 'Escolha a wallet', choose_source_wallet: 'Escolha a wallet',
refill_lnd_balance: 'Carregar o saldo da Lightning wallet', refill_lnd_balance: 'Carregar o saldo da Lightning wallet',
refill: 'Carregar', refill: 'Carregar',
placeholder: 'Invoice',
withdraw: 'Transferir', withdraw: 'Transferir',
expired: 'Expired', expired: 'Expired',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',

View file

@ -219,6 +219,7 @@ module.exports = {
refill: 'Пополнить', refill: 'Пополнить',
withdraw: 'Вывести', withdraw: 'Вывести',
expired: 'Истекший', expired: 'Истекший',
placeholder: 'Invoice',
sameWalletAsInvoiceError: 'Ты не можешь оплатить счет тем же кошельком, который ты использовал для его создания.', sameWalletAsInvoiceError: 'Ты не можешь оплатить счет тем же кошельком, который ты использовал для его создания.',
}, },
}; };

View file

@ -214,6 +214,7 @@ module.exports = {
refill_lnd_balance: 'เติมกระเป๋าสตางค์ไลท์นิง', refill_lnd_balance: 'เติมกระเป๋าสตางค์ไลท์นิง',
refill: 'เติม', refill: 'เติม',
withdraw: 'ถอน', withdraw: 'ถอน',
placeholder: 'Invoice',
expired: 'หมดอายุแล้ว', expired: 'หมดอายุแล้ว',
sameWalletAsInvoiceError: 'คุณไม่สามารถจ่ายใบแจ้งหนี้นี้ด้วยกระเป๋าสตางค์อันเดียวกันกับที่ใช้สร้างมัน.', sameWalletAsInvoiceError: 'คุณไม่สามารถจ่ายใบแจ้งหนี้นี้ด้วยกระเป๋าสตางค์อันเดียวกันกับที่ใช้สร้างมัน.',
}, },

View file

@ -217,6 +217,7 @@ module.exports = {
choose_source_wallet: 'Оберіть гаманець с якого слати', choose_source_wallet: 'Оберіть гаманець с якого слати',
refill_lnd_balance: 'Збільшити баланс Lightning гаманця', refill_lnd_balance: 'Збільшити баланс Lightning гаманця',
refill: 'Поповнити', refill: 'Поповнити',
placeholder: 'Invoice',
withdraw: 'Вивести', withdraw: 'Вивести',
expired: 'Expired', expired: 'Expired',
sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.', sameWalletAsInvoiceError: 'You can not pay an invoice with the same wallet used to create it.',

215
loc/zh_cn.js Executable file
View file

@ -0,0 +1,215 @@
module.exports = {
_: {
storage_is_encrypted: '你的信息已经被加密, 请输入密码解密',
enter_password: '输入密码',
bad_password: '密码无效,请重试',
never: '不',
continue: '继续',
ok: '好的',
},
wallets: {
select_wallet: '选择钱包',
options: '选项',
createBitcoinWallet: '您当前没有bitcoin钱包. 为了支持闪电钱包, 我们需要创建或者导入一个比特币钱包. 是否需要继续?',
list: {
app_name: 'BlueWallet',
title: '钱包',
header: '一个钱包代表一对的私钥和地址' + '你可以通过分享收款.',
add: '添加钱包',
create_a_wallet: '创建一个钱包',
create_a_wallet1: '创建钱包是免费的,你可以',
create_a_wallet2: '想创建多少就创建多少个',
latest_transaction: '最近的转账',
empty_txs1: '你的转账信息将展示在这里',
empty_txs2: '当前无信息',
tap_here_to_buy: '点击购买比特币',
},
reorder: {
title: '重新排列钱包',
},
add: {
title: '添加钱包',
description: '你可以扫描你的纸质备份钱包 (WIF格式), 或者创建一个新钱包. 默认支持隔离见证钱包',
scan: '扫描',
create: '创建',
label_new_segwit: '新隔离见证(Segwit)',
label_new_lightning: '新闪电',
wallet_name: '钱包名称',
wallet_type: '类型',
or: '或',
import_wallet: '导入钱包',
imported: '已经导入',
coming_soon: '即将来临',
lightning: '闪电',
bitcoin: '比特币',
},
details: {
title: '钱包',
address: '地址',
type: '类型',
label: '标签',
destination: '目的',
description: '描述',
are_you_sure: '你确认么?',
yes_delete: '是的,删除',
no_cancel: '不,取消',
delete: '删除',
save: '保存',
delete_this_wallet: '删除这个钱包',
export_backup: '导出备份',
buy_bitcoin: '购买比特币',
show_xpub: '展示钱包 XPUB',
},
export: {
title: '钱包导出',
},
xpub: {
title: '钱包 XPUB',
copiedToClipboard: '复制到粘贴板.',
},
import: {
title: '导入',
explanation: '输入你的助记词私钥或者WIF, 或者其他格式的数据. BlueWallet将尽可能的自动识别数据格式并导入钱包',
imported: '已经导入',
error: '导入失败,请确认你提供的信息是有效的',
success: '成功',
do_import: '导入',
scan_qr: '或扫面二维码',
},
scanQrWif: {
go_back: '回退',
cancel: '取消',
decoding: '解码中',
input_password: '输入密码',
password_explain: '这是一个BIP38加密的私钥',
bad_password: '密码错误',
wallet_already_exists: '当前钱包已经存在',
bad_wif: 'WIF格式无效',
imported_wif: 'WIF已经导入',
with_address: ' 地址为',
imported_segwit: 'SegWit已经导入',
imported_legacy: 'Legacy已经导入',
imported_watchonly: '导入只读',
},
},
transactions: {
list: {
tabBarLabel: '转账',
title: '转账',
description: '当前所有钱包的转入和转出记录',
conf: '配置',
},
details: {
title: '转账',
from: '输入',
to: '输出',
copy: '复制',
transaction_details: '转账详情',
show_in_block_explorer: '区块浏览器展示',
},
},
send: {
header: '发送',
details: {
title: '创建交易',
amount_field_is_not_valid: '金额格式无效',
fee_field_is_not_valid: '费用格式无效',
address_field_is_not_valid: '地址内容无效',
total_exceeds_balance: '余额不足',
create_tx_error: '创建交易失败. 请确认地址格式正确.',
address: '地址',
amount_placeholder: '发送金额(in BTC)',
fee_placeholder: '手续费用 (in BTC)',
note_placeholder: '消息',
cancel: '取消',
scan: '扫描',
send: '发送',
create: '创建',
remaining_balance: '剩余金额',
},
confirm: {
header: '确认',
sendNow: '现在发送',
},
success: {
done: '完成',
},
create: {
details: '详情',
title: '创建详情',
error: '创建交易失败. 无效地址或金额?',
go_back: '回退',
this_is_hex: '这个是交易的十六进制数据, 签名并广播到全网络.',
to: '到',
amount: '金额',
fee: '手续费',
tx_size: '交易大小',
satoshi_per_byte: '葱每byte',
memo: '消息',
broadcast: '广播',
not_enough_fee: '手续费不够,请增加手续费',
},
},
receive: {
header: '收款',
details: {
title: '分享这个地址给付款人',
share: '分享',
copiedToClipboard: '复制到粘贴板.',
label: '描述',
create: '创建',
setAmount: '收款金额',
},
},
buyBitcoin: {
header: '购买比特币',
tap_your_address: '点击地址复制到粘贴板:',
copied: '复制到粘贴板!',
},
settings: {
header: '设置',
plausible_deniability: '可否认性...',
storage_not_encrypted: '存储:未加密',
storage_encrypted: '存储:加密中',
password: '密码',
password_explain: '创建你的加密密码',
retype_password: '再次输入密码',
passwords_do_not_match: '两次输入密码不同',
encrypt_storage: '加密存储',
lightning_settings: '闪电网络设置',
lightning_settings_explain: '如要要连接你自己的闪电节点请安装LndHub' + ' 并把url地址输入到下面. 空白将使用默认的LndHub (lndhub.io)',
save: '保存',
about: '关于',
language: '语言',
currency: '货币',
},
plausibledeniability: {
title: '可否认性',
help:
'在某些情况下, 你不得不暴露 ' +
'密码. 为了让你的比特币更加安全, BlueWallet可以创建一些 ' +
'加密空间, 用不同的密码. 在压力之下, ' +
'你可以暴露这个钱包密码. 再次进入 ' +
'BlueWallet, 我们会解锁一些虚拟空间. 对第三方来说看上去' +
'是合理的, 但会偷偷的帮你保证主钱包的安全 ' +
'币也就安全了.',
help2: '新的空间具备完整的功能,你可以存在 ' + '少量的金额在里面.',
create_fake_storage: '创建虚拟加密存储',
go_back: '回退',
create_password: '创建密码',
create_password_explanation: '虚拟存储空间密码不能和主存储空间密码相同',
password_should_not_match: '虚拟存储空间密码不能和主存储空间密码相同',
retype_password: '重输密码',
passwords_do_not_match: '两次输入密码不同,请重新输入',
success: '成功',
},
lnd: {
title: '配置资金支持',
choose_source_wallet: '选择一个资金源钱包',
refill_lnd_balance: '给闪电钱包充值',
refill: '充值',
withdraw: '提取',
expired: '超时',
sameWalletAsInvoiceError: '你不能用创建账单的钱包去支付该账单',
},
};

View file

@ -3,6 +3,7 @@ export const FiatUnit = Object.freeze({
AUD: { endPointKey: 'AUD', symbol: '$', locale: 'en-AU' }, AUD: { endPointKey: 'AUD', symbol: '$', locale: 'en-AU' },
BRL: { endPointKey: 'BRL', symbol: 'R$', locale: 'pt-BR' }, BRL: { endPointKey: 'BRL', symbol: 'R$', locale: 'pt-BR' },
CAD: { endPointKey: 'CAD', symbol: '$', locale: 'en-CA' }, CAD: { endPointKey: 'CAD', symbol: '$', locale: 'en-CA' },
CHF: { endPointKey: 'CHF', symbol: 'CHF', locale: 'de-CH' },
CZK: { endPointKey: 'CZK', symbol: 'Kč', locale: 'cs-CZ' }, CZK: { endPointKey: 'CZK', symbol: 'Kč', locale: 'cs-CZ' },
CNY: { endPointKey: 'CNY', symbol: '¥', locale: 'zh-CN' }, CNY: { endPointKey: 'CNY', symbol: '¥', locale: 'zh-CN' },
EUR: { endPointKey: 'EUR', symbol: '€', locale: 'en-EN' }, EUR: { endPointKey: 'EUR', symbol: '€', locale: 'en-EN' },

6690
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "BlueWallet", "name": "BlueWallet",
"version": "3.7.2", "version": "3.8.0",
"devDependencies": { "devDependencies": {
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"babel-jest": "^24.0.0", "babel-jest": "^24.0.0",
@ -36,67 +36,65 @@
} }
}, },
"dependencies": { "dependencies": {
"@babel/preset-env": "^7.3.1", "@babel/preset-env": "7.3.1",
"@remobile/react-native-qrcode-local-image": "^1.0.4", "@remobile/react-native-qrcode-local-image": "1.0.4",
"bignumber.js": "^8.0.2", "bignumber.js": "8.0.2",
"bip21": "^2.0.2", "bip21": "2.0.2",
"bip39": "^2.5.0", "bip39": "2.5.0",
"bitcoinjs-lib": "^3.3.2", "bitcoinjs-lib": "3.3.2",
"buffer": "^5.2.1", "buffer": "5.2.1",
"buffer-reverse": "^1.0.1", "buffer-reverse": "1.0.1",
"crypto-js": "^3.1.9-1", "crypto-js": "3.1.9-1",
"dayjs": "^1.8.0", "dayjs": "1.8.6",
"electrum-client": "git+https://github.com/Overtorment/node-electrum-client.git", "electrum-client": "git+https://github.com/Overtorment/node-electrum-client.git",
"eslint-config-prettier": "^4.0.0", "eslint-config-prettier": "4.0.0",
"eslint-config-standard": "^12.0.0", "eslint-config-standard": "12.0.0",
"eslint-config-standard-react": "^7.0.2", "eslint-config-standard-react": "7.0.2",
"eslint-plugin-prettier": "^3.0.1", "eslint-plugin-prettier": "3.0.1",
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-standard": "4.0.0",
"frisbee": "^2.0.5", "frisbee": "2.0.5",
"intl": "^1.2.5", "intl": "1.2.5",
"mocha": "^5.2.0", "mocha": "5.2.0",
"node-libs-react-native": "^1.0.1", "node-libs-react-native": "1.0.1",
"path-browserify": "^1.0.0", "path-browserify": "1.0.0",
"prettier": "^1.16.3", "prettier": "1.16.3",
"process": "^0.11.10", "process": "0.11.10",
"prop-types": "^15.6.2", "prop-types": "15.6.2",
"react": "^16.7.0", "react": "16.7.0",
"react-localization": "^1.0.10", "react-localization": "1.0.10",
"react-native": "^0.58.1", "react-native": "0.58.1",
"react-native-camera": "^1.10.0", "react-native-camera": "1.10.0",
"react-native-custom-qr-codes": "^2.0.0", "react-native-device-info": "0.26.1",
"react-native-device-info": "^0.26.1", "react-native-elements": "0.19.0",
"react-native-elements": "^0.19.0", "react-native-flexi-radio-button": "0.2.2",
"react-native-flexi-radio-button": "^0.2.2", "react-native-fs": "2.13.3",
"react-native-fs": "^2.13.3", "react-native-gesture-handler": "1.0.15",
"react-native-gesture-handler": "^1.0.15", "react-native-google-analytics-bridge": "7.0.0",
"react-native-google-analytics-bridge": "^7.0.0", "react-native-haptic-feedback": "1.5.0",
"react-native-haptic-feedback": "^1.5.0", "react-native-image-picker": "0.28.0",
"react-native-image-picker": "^0.28.0", "react-native-level-fs": "3.0.1",
"react-native-level-fs": "^3.0.1", "react-native-linear-gradient": "2.5.3",
"react-native-linear-gradient": "^2.5.3", "react-native-modal": "7.0.2",
"react-native-modal": "^7.0.2", "react-native-permissions": "1.1.1",
"react-native-permissions": "^1.1.1", "react-native-prompt-android": "0.3.4",
"react-native-prompt-android": "^0.3.4", "react-native-qrcode-svg": "5.1.2",
"react-native-qrcode": "^0.2.7", "react-native-randombytes": "3.5.2",
"react-native-randombytes": "^3.5.2", "react-native-rate": "1.1.6",
"react-native-rate": "^1.1.6", "react-native-sentry": "0.41.1",
"react-native-sentry": "^0.41.1", "react-native-snap-carousel": "3.7.5",
"react-native-snap-carousel": "^3.7.5",
"react-native-sortable-list": "0.0.22", "react-native-sortable-list": "0.0.22",
"react-native-svg": "^9.1.1", "react-native-svg": "9.1.1",
"react-native-tcp": "^3.3.0", "react-native-tcp": "3.3.0",
"react-native-vector-icons": "^6.2.0", "react-native-vector-icons": "6.2.0",
"react-native-webview": "4.1.0", "react-native-webview": "4.1.0",
"react-native-wkwebview-reborn": "^2.0.0", "react-native-wkwebview-reborn": "2.0.0",
"react-navigation": "^3.1.2", "react-navigation": "3.1.2",
"react-test-render": "^1.1.1", "react-test-render": "1.1.1",
"readable-stream": "^3.1.1", "readable-stream": "3.1.1",
"request-promise-native": "^1.0.5", "secure-random": "1.1.1",
"secure-random": "^1.1.1", "stream-browserify": "2.0.2",
"stream-browserify": "^2.0.2", "util": "0.11.1",
"util": "^0.11.1", "wif": "2.0.1"
"wif": "^2.0.1"
}, },
"react-native": { "react-native": {
"path": "path-browserify", "path": "path-browserify",

View file

@ -1,7 +1,7 @@
/* global alert */ /* global alert */
import React, { Component } from 'react'; import React, { Component } from 'react';
import { ActivityIndicator, View, TextInput, KeyboardAvoidingView, Keyboard, TouchableWithoutFeedback, Text } from 'react-native'; import { ActivityIndicator, View, TextInput, KeyboardAvoidingView, Keyboard, TouchableWithoutFeedback, Text } from 'react-native';
import { BlueNavigationStyle, BlueButton, BlueBitcoinAmount } from '../../BlueComponents'; import { BlueNavigationStyle, BlueButton, BlueBitcoinAmount, BlueDismissKeyboardInputAccessory } from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { BitcoinUnit } from '../../models/bitcoinUnits'; import { BitcoinUnit } from '../../models/bitcoinUnits';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
@ -47,7 +47,7 @@ export default class LNDCreateInvoice extends Component {
renderCreateButton = () => { renderCreateButton = () => {
return ( return (
<View style={{ paddingHorizontal: 56, paddingVertical: 16, alignContent: 'center', backgroundColor: '#FFFFFF' }}> <View style={{ marginHorizontal: 56, marginVertical: 16, minHeight: 45, alignContent: 'center', backgroundColor: '#FFFFFF' }}>
{this.state.isLoading ? ( {this.state.isLoading ? (
<ActivityIndicator /> <ActivityIndicator />
) : ( ) : (
@ -79,6 +79,7 @@ export default class LNDCreateInvoice extends Component {
}} }}
disabled={this.state.isLoading} disabled={this.state.isLoading}
unit={BitcoinUnit.SATS} unit={BitcoinUnit.SATS}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/> />
<View <View
style={{ style={{
@ -104,8 +105,10 @@ export default class LNDCreateInvoice extends Component {
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
editable={!this.state.isLoading} editable={!this.state.isLoading}
onSubmitEditing={Keyboard.dismiss} onSubmitEditing={Keyboard.dismiss}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/> />
</View> </View>
<BlueDismissKeyboardInputAccessory />
{this.renderCreateButton()} {this.renderCreateButton()}
</KeyboardAvoidingView> </KeyboardAvoidingView>
</View> </View>

View file

@ -11,7 +11,7 @@ import {
BlueSpacing20, BlueSpacing20,
} from '../../BlueComponents'; } from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { QRCode } from 'react-native-custom-qr-codes'; import QRCode from 'react-native-qrcode-svg';
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
const loc = require('../../loc'); const loc = require('../../loc');
@ -49,11 +49,12 @@ export default class LNDViewAdditionalInvoiceInformation extends Component {
<View style={{ flex: 1, justifyContent: 'space-between', alignItems: 'center' }}> <View style={{ flex: 1, justifyContent: 'space-between', alignItems: 'center' }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}> <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
<QRCode <QRCode
content={this.state.walletInfo.uris[0]} value={this.state.walletInfo.uris[0]}
size={300}
color={BlueApp.settings.foregroundColor}
backgroundColor={BlueApp.settings.brandingColor}
logo={require('../../img/qr-code.png')} logo={require('../../img/qr-code.png')}
size={300}
logoSize={90}
color={BlueApp.settings.foregroundColor}
logoBackgroundColor={BlueApp.settings.brandingColor}
/> />
<BlueSpacing20 /> <BlueSpacing20 />
<BlueText>Open direct channel with this node:</BlueText> <BlueText>Open direct channel with this node:</BlueText>

View file

@ -12,11 +12,11 @@ import {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { Icon } from 'react-native-elements'; import { Icon } from 'react-native-elements';
import QRCode from 'react-native-qrcode-svg';
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
const loc = require('../../loc'); const loc = require('../../loc');
const EV = require('../../events'); const EV = require('../../events');
const QRFast = require('react-native-qrcode');
const { width, height } = Dimensions.get('window'); const { width, height } = Dimensions.get('window');
export default class LNDViewInvoice extends Component { export default class LNDViewInvoice extends Component {
@ -42,7 +42,7 @@ export default class LNDViewInvoice extends Component {
qrCodeHeight: height > width ? width - 20 : width / 2, qrCodeHeight: height > width ? width - 20 : width / 2,
}; };
this.fetchInvoiceInterval = undefined; this.fetchInvoiceInterval = undefined;
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton); BackHandler.addEventListener('hardwareBackPress', this.handleBackButton.bind(this));
} }
async componentDidMount() { async componentDidMount() {
@ -92,11 +92,11 @@ export default class LNDViewInvoice extends Component {
componentWillUnmount() { componentWillUnmount() {
clearInterval(this.fetchInvoiceInterval); clearInterval(this.fetchInvoiceInterval);
this.fetchInvoiceInterval = undefined; this.fetchInvoiceInterval = undefined;
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton); BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton.bind(this));
} }
handleBackButton() { handleBackButton() {
this.props.navigation.dismiss(); this.props.navigation.popToTop();
return true; return true;
} }
@ -187,11 +187,13 @@ export default class LNDViewInvoice extends Component {
onLayout={this.onLayout} onLayout={this.onLayout}
> >
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 16 }}> <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 16 }}>
<QRFast <QRCode
value={typeof this.state.invoice === 'object' ? invoice.payment_request : invoice} value={typeof this.state.invoice === 'object' ? invoice.payment_request : invoice}
fgColor={BlueApp.settings.brandingColor} logo={require('../../img/qr-code.png')}
bgColor={BlueApp.settings.foregroundColor}
size={this.state.qrCodeHeight} size={this.state.qrCodeHeight}
logoSize={90}
color={BlueApp.settings.foregroundColor}
logoBackgroundColor={BlueApp.settings.brandingColor}
/> />
</View> </View>
@ -239,6 +241,6 @@ LNDViewInvoice.propTypes = {
goBack: PropTypes.func, goBack: PropTypes.func,
navigate: PropTypes.func, navigate: PropTypes.func,
getParam: PropTypes.func, getParam: PropTypes.func,
dismiss: PropTypes.func, popToTop: PropTypes.func,
}), }),
}; };

View file

@ -110,6 +110,7 @@ export default class ScanLndInvoice extends React.Component {
isLoading: false, isLoading: false,
}); });
} catch (Err) { } catch (Err) {
Keyboard.dismiss();
this.setState({ isLoading: false }); this.setState({ isLoading: false });
alert(Err.message); alert(Err.message);
} }
@ -209,6 +210,7 @@ export default class ScanLndInvoice extends React.Component {
onBarScanned={this.processInvoice} onBarScanned={this.processInvoice}
address={this.state.destination} address={this.state.destination}
isLoading={this.state.isLoading} isLoading={this.state.isLoading}
placeholder={loc.lnd.placeholder}
/> />
<View <View
style={{ style={{

View file

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { View, Share } from 'react-native'; import { View, Share } from 'react-native';
import { QRCode } from 'react-native-custom-qr-codes'; import QRCode from 'react-native-qrcode-svg';
import bip21 from 'bip21'; import bip21 from 'bip21';
import { import {
BlueLoading, BlueLoading,
@ -36,10 +36,10 @@ export default class ReceiveDetails extends Component {
addressText: '', addressText: '',
}; };
// EV(EV.enum.RECEIVE_ADDRESS_CHANGED, this.refreshFunction.bind(this)); // EV(EV.enum.RECEIVE_ADDRESS_CHANGED, this.redrawScreen.bind(this));
} }
/* refreshFunction(newAddress) { /* redrawScreen(newAddress) {
console.log('newAddress =', newAddress); console.log('newAddress =', newAddress);
this.setState({ this.setState({
address: newAddress, address: newAddress,
@ -88,11 +88,12 @@ export default class ReceiveDetails extends Component {
<View style={{ flex: 1, justifyContent: 'space-between', alignItems: 'center' }}> <View style={{ flex: 1, justifyContent: 'space-between', alignItems: 'center' }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}> <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
<QRCode <QRCode
content={bip21.encode(this.state.address)} value={bip21.encode(this.state.address)}
size={(is.ipad() && 300) || 300}
color={BlueApp.settings.foregroundColor}
backgroundColor={BlueApp.settings.brandingColor}
logo={require('../../img/qr-code.png')} logo={require('../../img/qr-code.png')}
size={(is.ipad() && 300) || 300}
logoSize={90}
color={BlueApp.settings.foregroundColor}
logoBackgroundColor={BlueApp.settings.brandingColor}
/> />
<BlueCopyTextToClipboard text={this.state.addressText} /> <BlueCopyTextToClipboard text={this.state.addressText} />
</View> </View>

View file

@ -1,7 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { View, Share, TextInput, KeyboardAvoidingView, Platform, Dimensions, ScrollView } from 'react-native'; import { View, Share, TextInput, KeyboardAvoidingView, Dimensions, ScrollView } from 'react-native';
import { QRCode as QRSlow } from 'react-native-custom-qr-codes'; import QRCode from 'react-native-qrcode-svg';
import QRFast from 'react-native-qrcode';
import bip21 from 'bip21'; import bip21 from 'bip21';
import { import {
SafeBlueArea, SafeBlueArea,
@ -105,23 +104,15 @@ export default class ReceiveAmount extends Component {
{this.state.label} {this.state.label}
</BlueText> </BlueText>
<View style={{ justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}> <View style={{ justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
{Platform.OS === 'ios' || this.state.bip21.length < 54 ? ( <QRCode
<QRSlow value={this.state.bip21}
content={this.state.bip21} logo={require('../../img/qr-code.png')}
size={this.determineSize()} size={this.determineSize()}
color={BlueApp.settings.foregroundColor} logoSize={90}
backgroundColor={BlueApp.settings.brandingColor} color={BlueApp.settings.foregroundColor}
logo={require('../../img/qr-code.png')} logoBackgroundColor={BlueApp.settings.brandingColor}
ecl={'Q'} ecl={'Q'}
/> />
) : (
<QRFast
value={this.state.bip21}
size={this.determineSize()}
fgColor={BlueApp.settings.brandingColor}
bgColor={BlueApp.settings.foregroundColor}
/>
)}
</View> </View>
<View style={{ alignItems: 'center', justifyContent: 'space-between' }}> <View style={{ alignItems: 'center', justifyContent: 'space-between' }}>
<BlueCopyTextToClipboard text={this.state.bip21} /> <BlueCopyTextToClipboard text={this.state.bip21} />

View file

@ -4,6 +4,7 @@ import {
ActivityIndicator, ActivityIndicator,
View, View,
TextInput, TextInput,
StatusBar,
TouchableOpacity, TouchableOpacity,
KeyboardAvoidingView, KeyboardAvoidingView,
Keyboard, Keyboard,
@ -15,13 +16,20 @@ import {
Text, Text,
} from 'react-native'; } from 'react-native';
import { Icon } from 'react-native-elements'; import { Icon } from 'react-native-elements';
import { BlueNavigationStyle, BlueButton, BlueBitcoinAmount, BlueAddressInput } from '../../BlueComponents'; import {
BlueNavigationStyle,
BlueButton,
BlueBitcoinAmount,
BlueAddressInput,
BlueDismissKeyboardInputAccessory,
BlueLoading,
} from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Modal from 'react-native-modal'; import Modal from 'react-native-modal';
import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees'; import NetworkTransactionFees, { NetworkTransactionFee } from '../../models/networkTransactionFees';
import BitcoinBIP70TransactionDecode from '../../bip70/bip70'; import BitcoinBIP70TransactionDecode from '../../bip70/bip70';
import { BitcoinUnit } from '../../models/bitcoinUnits'; import { BitcoinUnit } from '../../models/bitcoinUnits';
import { HDLegacyP2PKHWallet, HDSegwitP2SHWallet } from '../../class'; import { HDLegacyP2PKHWallet, HDSegwitP2SHWallet, LightningCustodianWallet } from '../../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
const bip21 = require('bip21'); const bip21 = require('bip21');
let BigNumber = require('bignumber.js'); let BigNumber = require('bignumber.js');
@ -38,6 +46,8 @@ export default class SendDetails extends Component {
title: loc.send.header, title: loc.send.header,
}); });
state = { isLoading: true, fromWallet: undefined };
constructor(props) { constructor(props) {
super(props); super(props);
console.log('props.navigation.state.params=', props.navigation.state.params); console.log('props.navigation.state.params=', props.navigation.state.params);
@ -51,38 +61,50 @@ export default class SendDetails extends Component {
if (props.navigation.state.params) fromSecret = props.navigation.state.params.fromSecret; if (props.navigation.state.params) fromSecret = props.navigation.state.params.fromSecret;
let fromWallet = null; let fromWallet = null;
const wallets = BlueApp.getWallets(); const wallets = BlueApp.getWallets().filter(wallet => wallet.type !== LightningCustodianWallet.type);
for (let w of wallets) { if (wallets.length === 0) {
if (w.getSecret() === fromSecret) { alert('Before creating a transaction, you must first add a Bitcoin wallet.');
fromWallet = w; return props.navigation.goBack(null);
break; } else {
if (!fromWallet && wallets.length > 0) {
fromWallet = wallets[0];
fromAddress = fromWallet.getAddress();
fromSecret = fromWallet.getSecret();
}
if (fromWallet === null) return props.navigation.goBack(null);
for (let w of wallets) {
if (w.getSecret() === fromSecret) {
fromWallet = w;
break;
}
if (w.getAddress() === fromAddress) {
fromWallet = w;
}
} }
if (w.getAddress() === fromAddress) { this.state = {
fromWallet = w; isFeeSelectionModalVisible: false,
} fromAddress,
fromWallet,
fromSecret,
address,
memo,
fee: 1,
networkTransactionFees: new NetworkTransactionFee(1, 1, 1),
feeSliderValue: 1,
bip70TransactionExpiration: null,
renderWalletSelectionButtonHidden: false,
};
} }
// fallback to first wallet if it exists
if (!fromWallet && wallets[0]) fromWallet = wallets[0];
this.state = {
isFeeSelectionModalVisible: false,
fromAddress,
fromWallet,
fromSecret,
isLoading: false,
address,
memo,
fee: 1,
networkTransactionFees: new NetworkTransactionFee(1, 1, 1),
feeSliderValue: 1,
bip70TransactionExpiration: null,
renderWalletSelectionButtonHidden: false,
};
} }
/**
* TODO: refactor this mess, get rid of regexp, use https://github.com/bitcoinjs/bitcoinjs-lib/issues/890 etc etc
*
* @param data {String} Can be address or `bitcoin:xxxxxxx` uri scheme, or invalid garbage
*/
processAddressData = data => { processAddressData = data => {
this.setState( this.setState(
{ isLoading: true }, { isLoading: true },
@ -91,7 +113,7 @@ export default class SendDetails extends Component {
this.processBIP70Invoice(data); this.processBIP70Invoice(data);
} else { } else {
const dataWithoutSchema = data.replace('bitcoin:', ''); const dataWithoutSchema = data.replace('bitcoin:', '');
if (btcAddressRx.test(dataWithoutSchema) || dataWithoutSchema.indexOf('bc1') === 0) { if (btcAddressRx.test(dataWithoutSchema) || (dataWithoutSchema.indexOf('bc1') === 0 && dataWithoutSchema.indexOf('?') === -1)) {
this.setState({ this.setState({
address: dataWithoutSchema, address: dataWithoutSchema,
bip70TransactionExpiration: null, bip70TransactionExpiration: null,
@ -111,7 +133,7 @@ export default class SendDetails extends Component {
this.setState({ isLoading: false }); this.setState({ isLoading: false });
} }
console.log(options); console.log(options);
if (btcAddressRx.test(address)) { if (btcAddressRx.test(address) || address.indexOf('bc1') === 0) {
this.setState({ this.setState({
address, address,
amount: options.amount, amount: options.amount,
@ -128,6 +150,7 @@ export default class SendDetails extends Component {
}; };
async componentDidMount() { async componentDidMount() {
StatusBar.setBarStyle('dark-content');
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow); this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide); this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
try { try {
@ -284,6 +307,7 @@ export default class SendDetails extends Component {
} }
async createTransaction() { async createTransaction() {
Keyboard.dismiss();
this.setState({ isLoading: true }); this.setState({ isLoading: true });
let error = false; let error = false;
let requestedSatPerByte = this.state.fee.toString().replace(/\D/g, ''); let requestedSatPerByte = this.state.fee.toString().replace(/\D/g, '');
@ -450,6 +474,7 @@ export default class SendDetails extends Component {
placeholderTextColor="#37c0a1" placeholderTextColor="#37c0a1"
placeholder={this.state.networkTransactionFees.halfHourFee.toString()} placeholder={this.state.networkTransactionFees.halfHourFee.toString()}
style={{ fontWeight: '600', color: '#37c0a1', marginBottom: 0, marginRight: 4, textAlign: 'right', fontSize: 36 }} style={{ fontWeight: '600', color: '#37c0a1', marginBottom: 0, marginRight: 4, textAlign: 'right', fontSize: 36 }}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/> />
<Text <Text
style={{ style={{
@ -529,10 +554,10 @@ export default class SendDetails extends Component {
}; };
render() { render() {
if (!this.state.fromWallet.getAddress) { if (this.state.isLoading || typeof this.state.fromWallet === 'undefined') {
return ( return (
<View style={{ flex: 1, paddingTop: 20 }}> <View style={{ flex: 1, paddingTop: 20 }}>
<Text>System error: Source wallet not found (this should never happen)</Text> <BlueLoading />
</View> </View>
); );
} }
@ -545,6 +570,7 @@ export default class SendDetails extends Component {
isLoading={this.state.isLoading} isLoading={this.state.isLoading}
amount={this.state.amount} amount={this.state.amount}
onChangeText={text => this.setState({ amount: text })} onChangeText={text => this.setState({ amount: text })}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/> />
<BlueAddressInput <BlueAddressInput
onChangeText={text => { onChangeText={text => {
@ -566,6 +592,7 @@ export default class SendDetails extends Component {
onBarScanned={this.processAddressData} onBarScanned={this.processAddressData}
address={this.state.address} address={this.state.address}
isLoading={this.state.isLoading} isLoading={this.state.isLoading}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/> />
<View <View
hide={!this.state.showMemoRow} hide={!this.state.showMemoRow}
@ -592,6 +619,7 @@ export default class SendDetails extends Component {
style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }} style={{ flex: 1, marginHorizontal: 8, minHeight: 33 }}
editable={!this.state.isLoading} editable={!this.state.isLoading}
onSubmitEditing={Keyboard.dismiss} onSubmitEditing={Keyboard.dismiss}
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
/> />
</View> </View>
<TouchableOpacity <TouchableOpacity
@ -620,6 +648,7 @@ export default class SendDetails extends Component {
{this.renderFeeSelectionModal()} {this.renderFeeSelectionModal()}
</KeyboardAvoidingView> </KeyboardAvoidingView>
</View> </View>
<BlueDismissKeyboardInputAccessory />
{this.renderWalletSelectionButton()} {this.renderWalletSelectionButton()}
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
@ -657,6 +686,7 @@ const styles = StyleSheet.create({
SendDetails.propTypes = { SendDetails.propTypes = {
navigation: PropTypes.shape({ navigation: PropTypes.shape({
pop: PropTypes.func, pop: PropTypes.func,
goBack: PropTypes.func,
navigate: PropTypes.func, navigate: PropTypes.func,
getParam: PropTypes.func, getParam: PropTypes.func,
state: PropTypes.shape({ state: PropTypes.shape({

View file

@ -121,7 +121,7 @@ Success.propTypes = {
dismiss: PropTypes.func, dismiss: PropTypes.func,
state: PropTypes.shape({ state: PropTypes.shape({
params: PropTypes.shape({ params: PropTypes.shape({
amount: PropTypes.string, amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
fee: PropTypes.number, fee: PropTypes.number,
}), }),
}), }),

View file

@ -19,11 +19,13 @@ export default class Language extends Component {
availableLanguages: [ availableLanguages: [
{ label: 'English', value: 'en' }, { label: 'English', value: 'en' },
{ label: 'Česky (CZ)', value: 'cs_cz' }, { label: 'Česky (CZ)', value: 'cs_cz' },
{ label: 'Chinese (ZH)', value: 'zh_cn' },
{ label: 'Croatian (HR)', value: 'hr_hr' }, { label: 'Croatian (HR)', value: 'hr_hr' },
{ label: 'Danish (DK)', value: 'da_dk' }, { label: 'Danish (DK)', value: 'da_dk' },
{ label: 'Deutsch (DE)', value: 'de_de' }, { label: 'Deutsch (DE)', value: 'de_de' },
{ label: 'Español (ES)', value: 'es' }, { label: 'Español (ES)', value: 'es' },
{ label: 'Français (FR)', value: 'fr_fr' }, { label: 'Français (FR)', value: 'fr_fr' },
{ label: 'Indonesia (ID)', value: 'id_id' },
{ label: '日本語 (JP)', value: 'jp_jp' }, { label: '日本語 (JP)', value: 'jp_jp' },
{ label: 'Nederlands (NL)', value: 'nl_nl' }, { label: 'Nederlands (NL)', value: 'nl_nl' },
{ label: 'Portuguese (BR)', value: 'pt_br' }, { label: 'Portuguese (BR)', value: 'pt_br' },

View file

@ -1,9 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Dimensions, Platform, ActivityIndicator, View } from 'react-native'; import { Dimensions, ActivityIndicator, View } from 'react-native';
import { QRCode as QRSlow } from 'react-native-custom-qr-codes'; import QRCode from 'react-native-qrcode-svg';
import { BlueSpacing20, SafeBlueArea, BlueNavigationStyle, BlueText } from '../../BlueComponents'; import { BlueSpacing20, SafeBlueArea, BlueNavigationStyle, BlueText } from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const QRFast = require('react-native-qrcode');
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
let loc = require('../../loc'); let loc = require('../../loc');
@ -81,27 +80,17 @@ export default class WalletExport extends Component {
<BlueSpacing20 /> <BlueSpacing20 />
{(() => { {(() => {
if (this.state.showQr) { if (this.state.showQr) {
if (Platform.OS === 'ios' || this.state.wallet.getSecret().length < 54) { return (
return ( <QRCode
<QRSlow value={this.state.wallet.getSecret()}
content={this.state.wallet.getSecret()} logo={require('../../img/qr-code.png')}
size={this.state.qrCodeHeight} size={this.state.qrCodeHeight}
color={BlueApp.settings.foregroundColor} logoSize={90}
backgroundColor={BlueApp.settings.brandingColor} color={BlueApp.settings.foregroundColor}
logo={require('../../img/qr-code.png')} logoBackgroundColor={BlueApp.settings.brandingColor}
ecl={'H'} ecl={'H'}
/> />
); );
} else {
return (
<QRFast
value={this.state.wallet.getSecret()}
size={this.state.qrCodeHeight}
fgColor={BlueApp.settings.brandingColor}
bgColor={BlueApp.settings.foregroundColor}
/>
);
}
} else { } else {
return ( return (
<View> <View>

View file

@ -1,6 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { View, TouchableOpacity, Text, FlatList, RefreshControl, ScrollView } from 'react-native'; import { View, TouchableOpacity, Text, FlatList, InteractionManager, RefreshControl, ScrollView } from 'react-native';
import { BlueLoading, SafeBlueArea, WalletsCarousel, BlueList, BlueHeaderDefaultMain, BlueListTransactionItem } from '../../BlueComponents'; import { BlueLoading, SafeBlueArea, WalletsCarousel, BlueList, BlueHeaderDefaultMain, BlueTransactionListItem } from '../../BlueComponents';
import { Icon } from 'react-native-elements'; import { Icon } from 'react-native-elements';
import { NavigationEvents } from 'react-navigation'; import { NavigationEvents } from 'react-navigation';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
@ -33,18 +33,19 @@ export default class WalletsList extends Component {
super(props); super(props);
this.state = { this.state = {
isLoading: true, isLoading: true,
isFlatListRefreshControlHidden: true,
wallets: BlueApp.getWallets().concat(false), wallets: BlueApp.getWallets().concat(false),
lastSnappedTo: 0, lastSnappedTo: 0,
}; };
EV(EV.enum.WALLETS_COUNT_CHANGED, this.refreshFunction.bind(this)); EV(EV.enum.WALLETS_COUNT_CHANGED, this.redrawScreen.bind(this));
// here, when we receive TRANSACTIONS_COUNT_CHANGED we do not query // here, when we receive TRANSACTIONS_COUNT_CHANGED we do not query
// remote server, we just redraw the screen // remote server, we just redraw the screen
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED, this.refreshFunction.bind(this)); EV(EV.enum.TRANSACTIONS_COUNT_CHANGED, this.redrawScreen.bind(this));
} }
componentDidMount() { componentDidMount() {
this.refreshFunction(); this.redrawScreen();
} }
/** /**
@ -52,28 +53,26 @@ export default class WalletsList extends Component {
* Triggered manually by user on pull-to-refresh. * Triggered manually by user on pull-to-refresh.
*/ */
refreshTransactions() { refreshTransactions() {
if (!(this.lastSnappedTo < BlueApp.getWallets().length)) { if (!(this.lastSnappedTo < BlueApp.getWallets().length) && this.lastSnappedTo !== undefined) {
// last card, nop // last card, nop
console.log('last card, nop'); console.log('last card, nop');
return; return;
} }
this.setState( this.setState(
{ {
isTransactionsLoading: true, isFlatListRefreshControlHidden: true,
}, },
async function() { () => {
let that = this; InteractionManager.runAfterInteractions(async () => {
setTimeout(async function() {
// more responsive // more responsive
let noErr = true; let noErr = true;
try { try {
let balanceStart = +new Date(); let balanceStart = +new Date();
await BlueApp.fetchWalletBalances(that.lastSnappedTo || 0); await BlueApp.fetchWalletBalances(this.lastSnappedTo || 0);
let balanceEnd = +new Date(); let balanceEnd = +new Date();
console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec'); console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
let start = +new Date(); let start = +new Date();
await BlueApp.fetchWalletTransactions(that.lastSnappedTo || 0); await BlueApp.fetchWalletTransactions(this.lastSnappedTo || 0);
let end = +new Date(); let end = +new Date();
console.log('fetch tx took', (end - start) / 1000, 'sec'); console.log('fetch tx took', (end - start) / 1000, 'sec');
} catch (err) { } catch (err) {
@ -82,24 +81,22 @@ export default class WalletsList extends Component {
} }
if (noErr) await BlueApp.saveToDisk(); // caching if (noErr) await BlueApp.saveToDisk(); // caching
that.refreshFunction(); this.redrawScreen();
}, 1); });
}, },
); );
} }
/** redrawScreen() {
* Redraws the screen console.log('wallets/list redrawScreen()');
*/
refreshFunction() {
if (BlueApp.getBalance() !== 0) { if (BlueApp.getBalance() !== 0) {
A(A.ENUM.GOT_NONZERO_BALANCE); A(A.ENUM.GOT_NONZERO_BALANCE);
} }
this.setState({ this.setState({
isLoading: false, isLoading: false,
isTransactionsLoading: false, isFlatListRefreshControlHidden: true,
dataSource: BlueApp.getTransactions(), dataSource: BlueApp.getTransactions(null, 10),
wallets: BlueApp.getWallets().concat(false), wallets: BlueApp.getWallets().concat(false),
}); });
} }
@ -163,7 +160,7 @@ export default class WalletsList extends Component {
console.log('balance changed, thus txs too'); console.log('balance changed, thus txs too');
// balance changed, thus txs too // balance changed, thus txs too
await wallets[index].fetchTransactions(); await wallets[index].fetchTransactions();
this.refreshFunction(); this.redrawScreen();
didRefresh = true; didRefresh = true;
} else if (wallets[index].timeToRefreshTransaction()) { } else if (wallets[index].timeToRefreshTransaction()) {
console.log(wallets[index].getLabel(), 'thinks its time to refresh TXs'); console.log(wallets[index].getLabel(), 'thinks its time to refresh TXs');
@ -174,7 +171,7 @@ export default class WalletsList extends Component {
if (wallets[index].fetchUserInvoices) { if (wallets[index].fetchUserInvoices) {
await wallets[index].fetchUserInvoices(); await wallets[index].fetchUserInvoices();
} }
this.refreshFunction(); this.redrawScreen();
didRefresh = true; didRefresh = true;
} else { } else {
console.log('balance not changed'); console.log('balance not changed');
@ -218,8 +215,9 @@ export default class WalletsList extends Component {
} }
}; };
_renderItem = data => <BlueListTransactionItem item={data.item} itemPriceUnit={data.item.walletPreferredBalanceUnit} />; _renderItem = data => {
return <BlueTransactionListItem item={data.item} itemPriceUnit={data.item.walletPreferredBalanceUnit} />;
};
render() { render() {
if (this.state.isLoading) { if (this.state.isLoading) {
return <BlueLoading />; return <BlueLoading />;
@ -228,11 +226,13 @@ export default class WalletsList extends Component {
<SafeBlueArea style={{ flex: 1, backgroundColor: '#FFFFFF' }}> <SafeBlueArea style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
<NavigationEvents <NavigationEvents
onWillFocus={() => { onWillFocus={() => {
this.refreshFunction(); this.redrawScreen();
}} }}
/> />
<ScrollView <ScrollView
refreshControl={<RefreshControl onRefresh={() => this.refreshTransactions()} refreshing={this.state.isTransactionsLoading} />} refreshControl={
<RefreshControl onRefresh={() => this.refreshTransactions()} refreshing={!this.state.isFlatListRefreshControlHidden} />
}
> >
<BlueHeaderDefaultMain leftText={loc.wallets.list.title} onNewWalletPress={() => this.props.navigation.navigate('AddWallet')} /> <BlueHeaderDefaultMain leftText={loc.wallets.list.title} onNewWalletPress={() => this.props.navigation.navigate('AddWallet')} />
<WalletsCarousel <WalletsCarousel

View file

@ -205,7 +205,7 @@ export default class ScanQrWif extends React.Component {
alert(loc.wallets.scanQrWif.imported_wif + ret.data + loc.wallets.scanQrWif.with_address + newWallet.getAddress()); alert(loc.wallets.scanQrWif.imported_wif + ret.data + loc.wallets.scanQrWif.with_address + newWallet.getAddress());
} }
await BlueApp.saveToDisk(); await BlueApp.saveToDisk();
this.props.navigation.dismiss(); this.props.navigation.popToTop();
setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500); setTimeout(() => EV(EV.enum.WALLETS_COUNT_CHANGED), 500);
} // end } // end
@ -284,7 +284,6 @@ ScanQrWif.propTypes = {
navigation: PropTypes.shape({ navigation: PropTypes.shape({
goBack: PropTypes.func, goBack: PropTypes.func,
popToTop: PropTypes.func, popToTop: PropTypes.func,
dismiss: PropTypes.func,
navigate: PropTypes.func, navigate: PropTypes.func,
}), }),
}; };

View file

@ -1,9 +1,19 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Text, View, Image, FlatList, RefreshControl, TouchableOpacity, StatusBar } from 'react-native'; import {
Text,
View,
ActivityIndicator,
InteractionManager,
Image,
FlatList,
RefreshControl,
TouchableOpacity,
StatusBar,
} from 'react-native';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { NavigationEvents } from 'react-navigation'; import { NavigationEvents } from 'react-navigation';
import { BlueText, ManageFundsBigButton, BlueSendButtonIcon, BlueReceiveButtonIcon, BlueTransactionListItem } from '../../BlueComponents'; import { BlueSendButtonIcon, BlueReceiveButtonIcon, BlueTransactionListItem } from '../../BlueComponents';
import { Icon } from 'react-native-elements'; import { Icon } from 'react-native-elements';
import { BitcoinUnit } from '../../models/bitcoinUnits'; import { BitcoinUnit } from '../../models/bitcoinUnits';
import { LightningCustodianWallet } from '../../class'; import { LightningCustodianWallet } from '../../class';
@ -18,14 +28,14 @@ export default class WalletTransactions extends Component {
return { return {
headerRight: ( headerRight: (
<TouchableOpacity <TouchableOpacity
style={{ marginHorizontal: 8, minWidth: 150 }} style={{ marginHorizontal: 16, minWidth: 150, justifyContent: 'center', alignItems: 'flex-end' }}
onPress={() => onPress={() =>
navigation.navigate('WalletDetails', { navigation.navigate('WalletDetails', {
wallet: navigation.state.params.wallet, wallet: navigation.state.params.wallet,
}) })
} }
> >
<Text style={{ color: '#fff', fontSize: 20, fontWeight: '500', textAlign: 'right' }}>{loc.wallets.options}</Text> <Icon name="kebab-horizontal" size={22} type="octicon" color="#FFFFFF" />
</TouchableOpacity> </TouchableOpacity>
), ),
headerStyle: { headerStyle: {
@ -47,15 +57,17 @@ export default class WalletTransactions extends Component {
this.props.navigation.setParams({ wallet: wallet }); this.props.navigation.setParams({ wallet: wallet });
this.state = { this.state = {
isLoading: true, isLoading: true,
isTransactionsLoading: false, showShowFlatListRefreshControl: false,
wallet: wallet, wallet: wallet,
dataSource: wallet.getTransactions(), dataSource: this.getTransactions(15),
limit: 15,
pageSize: 20,
walletPreviousPreferredUnit: wallet.getPreferredBalanceUnit(), walletPreviousPreferredUnit: wallet.getPreferredBalanceUnit(),
}; };
} }
componentDidMount() { componentDidMount() {
this.refreshFunction(); // nop
} }
/** /**
@ -69,46 +81,34 @@ export default class WalletTransactions extends Component {
} }
/** /**
* Redraws the screen * Simple wrapper for `wallet.getTransactions()`, where `wallet` is current wallet.
* Sorts. Provides limiting.
*
* @param limit {Integer} How many txs return, starting from the earliest. Default: all of them.
* @returns {Array}
*/ */
refreshFunction() { getTransactions(limit = Infinity) {
setTimeout(() => { let wallet = this.props.navigation.getParam('wallet');
console.log('wallets/transactions refreshFunction()'); let txs = wallet.getTransactions();
let showSend = false; for (let tx of txs) {
let showReceive = false; tx.sort_ts = +new Date(tx.received);
const wallet = this.state.wallet; }
if (wallet) { txs = txs.sort(function(a, b) {
showSend = wallet.allowSend(); return b.sort_ts - a.sort_ts;
showReceive = wallet.allowReceive(); });
} return txs.slice(0, limit);
let showManageFundsBigButton = false; }
let showManageFundsSmallButton = false;
if (wallet && wallet.type === LightningCustodianWallet.type && wallet.getBalance() * 1 <= 0) {
showManageFundsBigButton = true;
showManageFundsSmallButton = false;
} else if (wallet && wallet.type === LightningCustodianWallet.type && wallet.getBalance() > 0) {
showManageFundsSmallButton = true;
showManageFundsBigButton = false;
}
let txs = wallet.getTransactions(); redrawScreen() {
for (let tx of txs) { InteractionManager.runAfterInteractions(async () => {
tx.sort_ts = +new Date(tx.received); console.log('wallets/transactions redrawScreen()');
}
txs = txs.sort(function(a, b) {
return b.sort_ts - a.sort_ts;
});
this.setState({ this.setState({
isLoading: false, isLoading: false,
isTransactionsLoading: false, showShowFlatListRefreshControl: false,
showReceiveButton: showReceive, dataSource: this.getTransactions(this.state.limit),
showSendButton: showSend,
showManageFundsBigButton,
showManageFundsSmallButton,
dataSource: txs,
}); });
}, 1); });
} }
isLightning() { isLightning() {
@ -124,49 +124,50 @@ export default class WalletTransactions extends Component {
* Forcefully fetches TXs and balance for wallet * Forcefully fetches TXs and balance for wallet
*/ */
refreshTransactions() { refreshTransactions() {
if (this.state.isLoading) return;
this.setState( this.setState(
{ {
isTransactionsLoading: true, showShowFlatListRefreshControl: true,
isLoading: true,
}, },
async function() { async () => {
let that = this; let noErr = true;
setTimeout(async function() { let smthChanged = false;
// more responsive try {
let noErr = true; /** @type {LegacyWallet} */
let smthChanged = false; let wallet = this.state.wallet;
try { let balanceStart = +new Date();
/** @type {LegacyWallet} */ const oldBalance = wallet.getBalance();
let wallet = that.state.wallet; await wallet.fetchBalance();
let balanceStart = +new Date(); if (oldBalance !== wallet.getBalance()) smthChanged = true;
const oldBalance = wallet.getBalance(); let balanceEnd = +new Date();
await wallet.fetchBalance(); console.log(wallet.getLabel(), 'fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
if (oldBalance !== wallet.getBalance()) smthChanged = true; let start = +new Date();
let balanceEnd = +new Date(); const oldTxLen = wallet.getTransactions().length;
console.log(wallet.getLabel(), 'fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec'); await wallet.fetchTransactions();
let start = +new Date(); if (oldTxLen !== wallet.getTransactions().length) smthChanged = true;
const oldTxLen = wallet.getTransactions().length; if (wallet.fetchPendingTransactions) {
await wallet.fetchTransactions(); await wallet.fetchPendingTransactions();
if (oldTxLen !== wallet.getTransactions().length) smthChanged = true;
if (wallet.fetchPendingTransactions) {
await wallet.fetchPendingTransactions();
}
if (wallet.fetchUserInvoices) {
await wallet.fetchUserInvoices();
}
let end = +new Date();
console.log(wallet.getLabel(), 'fetch tx took', (end - start) / 1000, 'sec');
} catch (err) {
noErr = false;
console.warn(err);
} }
if (noErr && smthChanged) { if (wallet.fetchUserInvoices) {
console.log('saving to disk'); await wallet.fetchUserInvoices();
await BlueApp.saveToDisk(); // caching
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); // let other components know they should redraw
} }
let end = +new Date();
that.refreshFunction(); // Redraws the screen console.log(wallet.getLabel(), 'fetch tx took', (end - start) / 1000, 'sec');
}, 1); } catch (err) {
noErr = false;
console.warn(err);
this.setState({
isLoading: false,
showShowFlatListRefreshControl: false,
});
}
if (noErr && smthChanged) {
console.log('saving to disk');
await BlueApp.saveToDisk(); // caching
EV(EV.enum.TRANSACTIONS_COUNT_CHANGED); // let other components know they should redraw
}
this.redrawScreen();
}, },
); );
} }
@ -193,7 +194,10 @@ export default class WalletTransactions extends Component {
renderWalletHeader = () => { renderWalletHeader = () => {
return ( return (
<LinearGradient colors={WalletGradient.gradientsFor(this.state.wallet.type)} style={{ padding: 15, minHeight: 164 }}> <LinearGradient
colors={WalletGradient.gradientsFor(this.state.wallet.type)}
style={{ padding: 15, minHeight: 140, justifyContent: 'center' }}
>
<Image <Image
source={ source={
(LightningCustodianWallet.type === this.state.wallet.type && require('../../img/lnd-shape.png')) || (LightningCustodianWallet.type === this.state.wallet.type && require('../../img/lnd-shape.png')) ||
@ -208,7 +212,6 @@ export default class WalletTransactions extends Component {
}} }}
/> />
<Text style={{ backgroundColor: 'transparent' }} />
<Text <Text
numberOfLines={1} numberOfLines={1}
style={{ style={{
@ -233,34 +236,45 @@ export default class WalletTransactions extends Component {
{loc.formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit(), true).toString()} {loc.formatBalance(this.state.wallet.getBalance(), this.state.wallet.getPreferredBalanceUnit(), true).toString()}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
<Text style={{ backgroundColor: 'transparent' }} /> {this.state.wallet.type === LightningCustodianWallet.type && (
<Text <TouchableOpacity onPress={() => this.props.navigation.navigate('ManageFunds', { fromWallet: this.state.wallet })}>
numberOfLines={1} <View
style={{ style={{
backgroundColor: 'transparent', marginTop: 14,
fontSize: 13, marginBottom: 10,
color: '#fff', backgroundColor: 'rgba(255,255,255,0.2)',
}} borderRadius: 9,
> minWidth: 119,
{loc.wallets.list.latest_transaction} minHeight: 39,
</Text> width: 119,
<Text height: 39,
numberOfLines={1} justifyContent: 'center',
style={{ alignItems: 'center',
backgroundColor: 'transparent', }}
fontWeight: 'bold', >
fontSize: 16, <Text
color: '#fff', style={{
}} fontWeight: '500',
> fontSize: 14,
{loc.transactionTimeToReadable(this.state.wallet.getLatestTransactionTime())} color: '#FFFFFF',
</Text> }}
>
{loc.lnd.title}
</Text>
</View>
</TouchableOpacity>
)}
</LinearGradient> </LinearGradient>
); );
}; };
_keyExtractor = (_item, index) => index.toString(); _keyExtractor = (_item, index) => index.toString();
renderListFooterComponent = () => {
// if not all txs rendered - display indicator
return (this.getTransactions(Infinity).length > this.state.limit && <ActivityIndicator />) || <View />;
};
renderListHeaderComponent = () => { renderListHeaderComponent = () => {
return ( return (
<View style={{ flexDirection: 'row', height: 50 }}> <View style={{ flexDirection: 'row', height: 50 }}>
@ -299,55 +313,57 @@ export default class WalletTransactions extends Component {
<NavigationEvents <NavigationEvents
onWillFocus={() => { onWillFocus={() => {
StatusBar.setBarStyle('light-content'); StatusBar.setBarStyle('light-content');
this.refreshFunction(); this.redrawScreen();
}} }}
onWillBlur={() => this.onWillBlur()} onWillBlur={() => this.onWillBlur()}
/> />
{this.renderWalletHeader()} {this.renderWalletHeader()}
<View style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
{(() => {
if (this.state.showManageFundsSmallButton) {
return (
<View style={{ justifyContent: 'space-between', alignContent: 'center', flexDirection: 'row', marginVertical: 8 }}>
<TouchableOpacity
style={{ left: 10, flexDirection: 'row', flex: 1, alignItems: 'center' }}
onPress={() => {
console.log('navigating to LappBrowser');
navigate('LappBrowser', { fromSecret: this.state.wallet.getSecret(), fromWallet: this.state.wallet });
}}
>
<BlueText style={{ fontWeight: '600', fontSize: 16 }}>marketplace</BlueText>
<Icon
name="shopping-cart"
type="font-awesome"
size={14}
color={BlueApp.settings.foregroundColor}
iconStyle={{ left: 5, top: 2 }}
/>
</TouchableOpacity>
<TouchableOpacity <View style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
style={{ marginRight: 10, flexDirection: 'row', alignItems: 'center' }} {this.state.wallet.type === LightningCustodianWallet.type && (
onPress={() => { <TouchableOpacity
console.log('navigating to', this.state.wallet.getLabel()); onPress={() => {
navigate('ManageFunds', { fromWallet: this.state.wallet }); console.log('navigating to LappBrowser');
}} navigate('LappBrowser', { fromSecret: this.state.wallet.getSecret(), fromWallet: this.state.wallet });
> }}
<BlueText style={{ fontWeight: '600', fontSize: 16 }}>{loc.lnd.title}</BlueText> >
<Icon <View
name="link" style={{
type="font-awesome" marginVertical: 16,
size={14} backgroundColor: '#f2f2f2',
color={BlueApp.settings.foregroundColor} borderRadius: 9,
iconStyle={{ left: 5, top: 2, transform: [{ rotate: '90deg' }] }} minWidth: 343,
/> minHeight: 49,
</TouchableOpacity> width: 343,
</View> height: 49,
); justifyContent: 'center',
} alignItems: 'center',
})()} alignSelf: 'center',
}}
>
<Text>marketplace</Text>
</View>
</TouchableOpacity>
)}
<FlatList <FlatList
onEndReachedThreshold={0.3}
onEndReached={() => {
// pagination in works. in this block we will add more txs to flatlist
// so as user scrolls closer to bottom it will render mode transactions
if (this.getTransactions(Infinity).length < this.state.limit) {
// all list rendered. nop
return;
}
this.setState({
dataSource: this.getTransactions(this.state.limit + this.state.pageSize),
limit: this.state.limit + this.state.pageSize,
pageSize: this.state.pageSize * 2,
});
}}
ListHeaderComponent={this.renderListHeaderComponent} ListHeaderComponent={this.renderListHeaderComponent}
ListFooterComponent={this.renderListFooterComponent}
ListEmptyComponent={ ListEmptyComponent={
<View style={{ top: 50, minHeight: 200, paddingHorizontal: 16 }}> <View style={{ top: 50, minHeight: 200, paddingHorizontal: 16 }}>
<Text <Text
@ -396,10 +412,11 @@ export default class WalletTransactions extends Component {
)} )}
</View> </View>
} }
refreshControl={<RefreshControl onRefresh={() => this.refreshTransactions()} refreshing={this.state.isTransactionsLoading} />} refreshControl={
<RefreshControl onRefresh={() => this.refreshTransactions()} refreshing={this.state.showShowFlatListRefreshControl} />
}
data={this.state.dataSource} data={this.state.dataSource}
keyExtractor={this._keyExtractor} keyExtractor={this._keyExtractor}
initialNumToRender={10}
renderItem={this.renderItem} renderItem={this.renderItem}
/> />
</View> </View>
@ -417,11 +434,11 @@ export default class WalletTransactions extends Component {
}} }}
> >
{(() => { {(() => {
if (this.state.showReceiveButton) { if (this.state.wallet.allowReceive()) {
return ( return (
<BlueReceiveButtonIcon <BlueReceiveButtonIcon
onPress={() => { onPress={() => {
if (this.state.wallet.type === new LightningCustodianWallet().type) { if (this.state.wallet.type === LightningCustodianWallet.type) {
navigate('LNDCreateInvoice', { fromWallet: this.state.wallet }); navigate('LNDCreateInvoice', { fromWallet: this.state.wallet });
} else { } else {
navigate('ReceiveDetails', { address: this.state.wallet.getAddress(), secret: this.state.wallet.getSecret() }); navigate('ReceiveDetails', { address: this.state.wallet.getAddress(), secret: this.state.wallet.getSecret() });
@ -433,7 +450,7 @@ export default class WalletTransactions extends Component {
})()} })()}
{(() => { {(() => {
if (this.state.showSendButton) { if (this.state.wallet.allowSend()) {
return ( return (
<BlueSendButtonIcon <BlueSendButtonIcon
onPress={() => { onPress={() => {
@ -447,19 +464,6 @@ export default class WalletTransactions extends Component {
); );
} }
})()} })()}
{(() => {
if (this.state.showManageFundsBigButton) {
return (
<ManageFundsBigButton
onPress={() => {
console.log('navigating to', this.state.wallet.getLabel());
navigate('ManageFunds', { fromWallet: this.state.wallet });
}}
/>
);
}
})()}
</View> </View>
</View> </View>
); );

View file

@ -1,9 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Dimensions, Platform, ActivityIndicator, View } from 'react-native'; import { Dimensions, ActivityIndicator, View } from 'react-native';
import { QRCode as QRSlow } from 'react-native-custom-qr-codes'; import QRCode from 'react-native-qrcode-svg';
import { BlueSpacing20, SafeBlueArea, BlueText, BlueNavigationStyle, BlueCopyTextToClipboard } from '../../BlueComponents'; import { BlueSpacing20, SafeBlueArea, BlueText, BlueNavigationStyle, BlueCopyTextToClipboard } from '../../BlueComponents';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const QRFast = require('react-native-qrcode');
/** @type {AppStorage} */ /** @type {AppStorage} */
let BlueApp = require('../../BlueApp'); let BlueApp = require('../../BlueApp');
let loc = require('../../loc'); let loc = require('../../loc');
@ -73,27 +72,17 @@ export default class WalletXpub extends Component {
{(() => { {(() => {
if (this.state.showQr) { if (this.state.showQr) {
if (Platform.OS === 'ios' || this.state.xpub.length < 54) { return (
return ( <QRCode
<QRSlow value={this.state.xpub}
content={this.state.xpub} logo={require('../../img/qr-code.png')}
color={BlueApp.settings.foregroundColor} size={this.state.qrCodeHeight}
backgroundColor={BlueApp.settings.brandingColor} logoSize={90}
logo={require('../../img/qr-code.png')} color={BlueApp.settings.foregroundColor}
size={this.state.qrCodeHeight} logoBackgroundColor={BlueApp.settings.brandingColor}
ecl={'H'} ecl={'H'}
/> />
); );
} else {
return (
<QRFast
value={this.state.xpub}
fgColor={BlueApp.settings.brandingColor}
bgColor={BlueApp.settings.foregroundColor}
size={this.state.qrCodeHeight}
/>
);
}
} else { } else {
return ( return (
<View> <View>