mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-01-19 05:45:15 +01:00
ADD: You can now pay for a Lightning invoice by manually inserting the invoice ID
This commit is contained in:
parent
8dbc3916d1
commit
d09dbb0935
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -46,6 +46,8 @@ module.exports = {
|
||||
label: 'Метка',
|
||||
are_you_sure: 'Вы уверены?',
|
||||
yes_delete: 'Да, удалить',
|
||||
destination: 'destination',
|
||||
description: 'description',
|
||||
no_cancel: 'Нет, отмена',
|
||||
delete_this_wallet: 'Удалить этот кошелек',
|
||||
export_backup: 'Экспорт / резервная копия',
|
||||
|
@ -44,6 +44,8 @@ module.exports = {
|
||||
address: 'Адреса',
|
||||
type: 'Тип',
|
||||
label: 'Мітка',
|
||||
destination: 'destination',
|
||||
description: 'description',
|
||||
are_you_sure: 'Ви впевнені?',
|
||||
yes_delete: 'Так, видалити',
|
||||
no_cancel: 'Ні, відміна',
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -400,6 +400,7 @@ export default class SendDetails extends Component {
|
||||
isLoading: true,
|
||||
},
|
||||
() => {
|
||||
Keyboard.dismiss();
|
||||
BitcoinBIP70TransactionDecode.decode(text).then(response => {
|
||||
this.setState({
|
||||
address: response.address,
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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={() => {
|
||||
|
@ -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',
|
||||
|
@ -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={() =>
|
||||
|
@ -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={() => {
|
||||
|
Loading…
Reference in New Issue
Block a user