ADD: You can now pay for a Lightning invoice by manually inserting the invoice ID

This commit is contained in:
Marcos Rodriguez Vélez 2018-10-22 18:51:30 -04:00 committed by Igor Korsakov
parent 8dbc3916d1
commit d09dbb0935
15 changed files with 170 additions and 113 deletions

View File

@ -59,9 +59,6 @@ const CreateTransactionStackNavigator = createStackNavigator({
SendDetails: {
screen: sendDetails,
},
ScanQrAddress: {
screen: sendScanQrAddress,
},
CreateTransaction: {
screen: sendCreate,
navigationOptions: {
@ -135,6 +132,9 @@ const Tabs = createStackNavigator(
ScanLndInvoice: {
screen: ScanLndInvoice,
},
ScanQrAddress: {
screen: sendScanQrAddress,
},
},
{
mode: 'modal',

View File

@ -44,6 +44,8 @@ module.exports = {
address: 'Address',
type: 'Type',
label: 'Label',
destination: 'destination',
description: 'description',
are_you_sure: 'Are you sure?',
yes_delete: 'Yes, delete',
no_cancel: 'No, cancel',

View File

@ -44,6 +44,8 @@ module.exports = {
address: 'Dirección',
type: 'Tipo',
label: 'Etiqueta',
destination: 'destino',
description: 'descripcion',
are_you_sure: '¿Estás seguro?',
yes_delete: 'Si, eliminar',
no_cancel: 'No, cancelar',

View File

@ -44,6 +44,8 @@ module.exports = {
title: 'wallet',
address: 'Endereço',
type: 'Tipo',
destination: 'destination',
description: 'description',
label: 'Nome',
are_you_sure: 'Tem a certeza?',
yes_delete: 'Sim, eliminar',

View File

@ -44,6 +44,8 @@ module.exports = {
address: 'Endereço',
type: 'Tipo',
label: 'Nome',
destination: 'destination',
description: 'description',
are_you_sure: 'Tem a certeza?',
yes_delete: 'Sim, eliminar',
no_cancel: 'Não, cancelar',

View File

@ -46,6 +46,8 @@ module.exports = {
label: 'Метка',
are_you_sure: 'Вы уверены?',
yes_delete: 'Да, удалить',
destination: 'destination',
description: 'description',
no_cancel: 'Нет, отмена',
delete_this_wallet: 'Удалить этот кошелек',
export_backup: 'Экспорт / резервная копия',

View File

@ -44,6 +44,8 @@ module.exports = {
address: 'Адреса',
type: 'Тип',
label: 'Мітка',
destination: 'destination',
description: 'description',
are_you_sure: 'Ви впевнені?',
yes_delete: 'Так, видалити',
no_cancel: 'Ні, відміна',

View File

@ -1,33 +1,25 @@
/* global alert */
import React from 'react';
import { Text, Dimensions, ActivityIndicator, Button, View, TouchableOpacity } from 'react-native';
import { BarCodeScanner, Permissions } from 'expo';
import { Text, Dimensions, ActivityIndicator, View, TouchableOpacity, TouchableWithoutFeedback, TextInput, Keyboard } from 'react-native';
import { Icon } from 'react-native-elements';
import PropTypes from 'prop-types';
import {
BlueSpacingVariable,
BlueFormInput,
BlueSpacing20,
BlueButton,
SafeBlueArea,
BlueCard,
BlueHeaderDefaultSub,
} from '../../BlueComponents';
import { BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueHeaderDefaultSub } from '../../BlueComponents';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let currency = require('../../currency');
let EV = require('../../events');
let loc = require('../../loc');
const { width } = Dimensions.get('window');
export default class ScanLndInvoice extends React.Component {
static navigationOptions = {
header: ({ navigation }) => {
return <View />;
return <BlueHeaderDefaultSub leftText={'Pay invoice'} onClose={() => navigation.goBack(null)} />;
},
};
state = {
isLoading: false,
hasCameraPermission: null,
};
constructor(props) {
@ -47,9 +39,13 @@ export default class ScanLndInvoice extends React.Component {
fromWallet,
fromSecret,
};
EV(EV.enum.CREATE_TRANSACTION_NEW_DESTINATION_ADDRESS, data => {
this.processInvoice(data);
});
}
async onBarCodeScanned(ret) {
async processInvoice(data) {
if (this.ignoreRead) return;
this.ignoreRead = true;
setTimeout(() => {
@ -61,8 +57,8 @@ export default class ScanLndInvoice extends React.Component {
return this.props.navigation.goBack();
}
ret.data = ret.data.replace('LIGHTNING:', '').replace('lightning:', '');
console.log(ret.data);
data = data.replace('LIGHTNING:', '').replace('lightning:', '');
console.log(data);
/**
* @type {LightningCustodianWallet}
@ -70,7 +66,7 @@ export default class ScanLndInvoice extends React.Component {
let w = this.state.fromWallet;
let decoded = false;
try {
decoded = await w.decodeInvoice(ret.data);
decoded = await w.decodeInvoice(data);
let expiresIn = (decoded.timestamp * 1 + decoded.expiry * 1) * 1000; // ms
if (+new Date() > expiresIn) {
@ -78,24 +74,22 @@ export default class ScanLndInvoice extends React.Component {
} else {
expiresIn = Math.round((expiresIn - +new Date()) / (60 * 1000)) + ' min';
}
Keyboard.dismiss();
this.setState({
isPaying: true,
invoice: ret.data,
invoice: data,
decoded,
expiresIn,
});
} catch (Err) {
alert(Err.message);
}
} // end
async componentWillMount() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ hasCameraPermission: status === 'granted' });
}
async pay() {
if (!this.state.hasOwnProperty('decoded')) {
return null;
}
let decoded = this.state.decoded;
/** @type {LightningCustodianWallet} */
@ -129,101 +123,140 @@ export default class ScanLndInvoice extends React.Component {
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<ActivityIndicator />
</View>
);
}
if (this.state.isPaying) {
return (
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<BlueSpacingVariable />
<BlueHeaderDefaultSub leftText={'Pay invoice'} onClose={() => this.props.navigation.goBack()} />
<BlueSpacing20 />
<Text style={{ textAlign: 'center', fontSize: 50, fontWeight: '700', color: '#2f5fb3' }}>
{currency.satoshiToLocalCurrency(this.state.decoded.num_satoshis)}
{this.state.hasOwnProperty('decoded') &&
this.state.decoded !== undefined &&
currency.satoshiToLocalCurrency(this.state.decoded.num_satoshis)}
</Text>
<Text style={{ textAlign: 'center', fontSize: 25, fontWeight: '600', color: '#d4d4d4' }}>
{currency.satoshiToBTC(this.state.decoded.num_satoshis)}
{this.state.hasOwnProperty('decoded') &&
this.state.decoded !== undefined &&
currency.satoshiToBTC(this.state.decoded.num_satoshis)}
</Text>
<BlueSpacing20 />
<BlueCard>
<BlueFormInput value={this.state.decoded.destination} />
<BlueFormInput value={this.state.decoded.description} />
<Text style={{ color: '#81868e', fontSize: 12, left: 20, top: 10 }}>Expires in: {this.state.expiresIn}</Text>
<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 (text.toLowerCase().startsWith('lnb')) {
this.processInvoice(text);
} else {
this.setState({ decoded: undefined, expiresIn: undefined });
}
}}
placeholder={loc.wallets.details.destination}
numberOfLines={1}
value={this.state.hasOwnProperty('decoded') && this.state.decoded !== undefined ? this.state.decoded.destination : ''}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33, height: 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
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 => {}}
placeholder={loc.wallets.details.description}
numberOfLines={1}
value={this.state.hasOwnProperty('decoded') && this.state.decoded !== undefined ? this.state.decoded.description : ''}
style={{ flex: 1, marginHorizontal: 8, minHeight: 33, height: 33 }}
editable={!this.state.isLoading}
/>
</View>
{this.state.expiresIn !== undefined && (
<Text style={{ color: '#81868e', fontSize: 12, left: 20, top: 10 }}>Expires in: {this.state.expiresIn}</Text>
)}
</BlueCard>
<BlueSpacing20 />
{(() => {
if (this.state.isPayingInProgress) {
return (
<View>
<ActivityIndicator />
</View>
);
} else {
return (
<BlueButton
icon={{
name: 'bolt',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
title={'Pay'}
buttonStyle={{ width: 150, left: (width - 150) / 2 - 20 }}
onPress={() => {
this.pay();
}}
/>
);
}
})()}
{this.state.hasOwnProperty('decoded') &&
this.state.decoded !== undefined &&
(() => {
if (this.state.isPayingInProgress) {
return (
<View>
<ActivityIndicator />
</View>
);
} else {
return (
<BlueButton
icon={{
name: 'bolt',
type: 'font-awesome',
color: BlueApp.settings.buttonTextColor,
}}
title={'Pay'}
buttonStyle={{ width: 150, left: (width - 150) / 2 - 20 }}
onPress={() => {
this.pay();
}}
/>
);
}
})()}
</SafeBlueArea>
);
}
const { hasCameraPermission } = this.state;
if (hasCameraPermission === null) {
return <View />;
} else if (hasCameraPermission === false) {
return <Text>No access to camera</Text>;
} else {
return (
<View style={{ flex: 1 }}>
<BarCodeScanner style={{ flex: 1 }} onBarCodeScanned={ret => this.onBarCodeScanned(ret)}>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
}}
>
<TouchableOpacity
style={{
flex: 0.2,
alignSelf: 'flex-end',
alignItems: 'center',
}}
>
<Button style={{ fontSize: 18, marginBottom: 10 }} title="Go back" onPress={() => this.props.navigation.goBack()} />
</TouchableOpacity>
</View>
</BarCodeScanner>
</View>
);
}
</TouchableWithoutFeedback>
);
}
}
ScanLndInvoice.propTypes = {
navigation: PropTypes.shape({
goBack: PropTypes.function,
navigate: PropTypes.function,
state: PropTypes.shape({
params: PropTypes.shape({
fromSecret: PropTypes.string,

View File

@ -85,7 +85,7 @@ export default class ReceiveDetails extends Component {
return (
<SafeBlueArea style={{ flex: 1 }}>
<View style={{ flex: 1, justifyContent: 'space-between', alignItems: 'center' }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 16 }}>
<QRCode
value={this.state.address}
size={(is.ipad() && 300) || 300}
@ -124,6 +124,7 @@ const styles = StyleSheet.create({
marginVertical: 32,
fontSize: 15,
color: '#9aa0aa',
textAlign: 'center',
},
});

View File

@ -400,6 +400,7 @@ export default class SendDetails extends Component {
isLoading: true,
},
() => {
Keyboard.dismiss();
BitcoinBIP70TransactionDecode.decode(text).then(response => {
this.setState({
address: response.address,

View File

@ -2,7 +2,9 @@ import React from 'react';
import { Text, ActivityIndicator, Button, View, TouchableOpacity } from 'react-native';
import { Permissions, BarCodeScanner } from 'expo';
import PropTypes from 'prop-types';
import { SafeBlueArea } from '../../BlueComponents';
let EV = require('../../events');
let loc = require('../../loc');
export default class CameraExample extends React.Component {
static navigationOptions = {
@ -46,7 +48,7 @@ export default class CameraExample extends React.Component {
return <Text>No access to camera</Text>;
} else {
return (
<View style={{ flex: 1 }}>
<SafeBlueArea style={{ flex: 1 }}>
<BarCodeScanner style={{ flex: 1 }} onBarCodeScanned={ret => this.onBarCodeScanned(ret)}>
<View
style={{
@ -57,16 +59,17 @@ export default class CameraExample extends React.Component {
>
<TouchableOpacity
style={{
flex: 0.2,
alignSelf: 'flex-end',
alignItems: 'center',
marginBottom: 20,
marginLeft: 16,
}}
>
<Button style={{ fontSize: 18, marginBottom: 10 }} title="Go back" onPress={() => this.props.navigation.goBack()} />
<Button style={{ fontSize: 18 }} title={loc.send.details.cancel} onPress={() => this.props.navigation.goBack()} />
</TouchableOpacity>
</View>
</BarCodeScanner>
</View>
</SafeBlueArea>
);
}
}

View File

@ -50,6 +50,7 @@ export default class About extends Component {
<ScrollView>
<BlueCard>
<BlueText h4>BlueWallet is free and opensource Bitcoin wallet. Licensed MIT.</BlueText>
<BlueSpacing20 />
<BlueButton
icon={{
@ -62,6 +63,7 @@ export default class About extends Component {
}}
title="github.com/BlueWallet/BlueWallet"
/>
<BlueSpacing20 />
<BlueButton
icon={{
@ -74,6 +76,7 @@ export default class About extends Component {
}}
title="Follow us on Twitter"
/>
<BlueSpacing20 />
<BlueButton
icon={{
@ -100,6 +103,7 @@ export default class About extends Component {
<BlueText h4>* rn-nodeify</BlueText>
<BlueText h4>* bignumber.js</BlueText>
<BlueText h4>* https://github.com/StefanoBalocco/isaac.js</BlueText>
<BlueSpacing20 />
<BlueButton
onPress={() => {

View File

@ -2,7 +2,7 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import { FormValidationMessage } from 'react-native-elements';
import { BlueLoading, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueHeaderDefaultSub } from '../../BlueComponents';
import { BlueLoading, BlueSpacing20, BlueButton, SafeBlueArea, BlueCard, BlueText, BlueHeaderDefaultSub } from '../../BlueComponents';
import PropTypes from 'prop-types';
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
@ -58,6 +58,7 @@ export default class EncryptStorage extends Component {
return (
<View>
<FormValidationMessage>{loc.settings.storage_not_encrypted}</FormValidationMessage>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'shield',

View File

@ -8,6 +8,7 @@ import {
BlueText,
BlueFormLabel,
BlueFormInputAddress,
BlueSpacing20,
BlueHeaderDefaultSub,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
@ -139,6 +140,7 @@ export default class WalletDetails extends Component {
} else {
return (
<View>
<BlueSpacing20 />
<BlueButton
icon={{
name: 'stop',
@ -150,6 +152,7 @@ export default class WalletDetails extends Component {
}}
title={loc.wallets.details.delete_this_wallet}
/>
<BlueSpacing20 />
<BlueButton
onPress={() =>

View File

@ -14,9 +14,9 @@ import {
BlueButtonLink,
BlueFormLabel,
BlueLoading,
BlueSpacingVariable,
BlueButton,
SafeBlueArea,
BlueSpacing20,
BlueHeaderDefaultSub,
} from '../../BlueComponents';
import PropTypes from 'prop-types';
@ -210,8 +210,8 @@ export default class WalletsImport extends Component {
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1, paddingTop: 40 }}>
<KeyboardAvoidingView behavior="position" enabled>
<BlueSpacingVariable />
<BlueFormLabel>{loc.wallets.import.explanation}</BlueFormLabel>
<BlueSpacing20 />
<BlueFormMultiInput
value={this.state.label}
placeholder={''}
@ -219,7 +219,7 @@ export default class WalletsImport extends Component {
this.setLabel(text);
}}
/>
<BlueSpacing20 />
<View
style={{
alignItems: 'center',
@ -241,7 +241,6 @@ export default class WalletsImport extends Component {
}, 1);
}}
/>
<BlueButtonLink
title={loc.wallets.import.scan_qr}
onPress={() => {