FIX: syncup with master, fix eslint errors

This commit is contained in:
Ivan Vershigora 2020-05-28 11:44:15 +03:00
parent 7d47682e0b
commit 1676c79185
26 changed files with 1043 additions and 536 deletions

View file

@ -394,70 +394,83 @@ export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure
);
};
export const BlueNavigationStyle = (navigation, withNavigationCloseButton = false, customCloseButtonFunction = undefined) => ({
headerStyle: {
backgroundColor: BlueApp.settings.brandingColor,
borderBottomWidth: 0,
elevation: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
color: BlueApp.settings.foregroundColor,
},
headerTintColor: BlueApp.settings.foregroundColor,
headerRight: withNavigationCloseButton
? () => (
export const BlueNavigationStyle = (navigation, withNavigationCloseButton = false, customCloseButtonFunction = undefined) => {
let headerRight;
if (withNavigationCloseButton) {
headerRight = () => (
<TouchableOpacity
style={{ width: 40, height: 40, padding: 14 }}
onPress={
customCloseButtonFunction === undefined
? () => {
Keyboard.dismiss();
navigation.goBack(null);
}
: customCloseButtonFunction
}
>
<Image style={{ alignSelf: 'center' }} source={require('./img/close.png')} />
</TouchableOpacity>
)
: null,
// headerBackTitle: null,
headerBackTitleVisible: false,
});
style={{ width: 40, height: 40, padding: 14 }}
onPress={
customCloseButtonFunction === undefined
? () => {
Keyboard.dismiss();
navigation.goBack(null);
}
: customCloseButtonFunction
}
>
<Image style={{ alignSelf: 'center' }} source={require('./img/close.png')} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuButton = false, advancedOptionsMenuButtonAction) => ({
headerStyle: {
backgroundColor: BlueApp.settings.brandingColor,
borderBottomWidth: 0,
elevation: 0,
},
headerTitleStyle: {
fontWeight: '600',
color: BlueApp.settings.foregroundColor,
},
headerTintColor: BlueApp.settings.foregroundColor,
headerLeft: () => (
<TouchableOpacity
style={{ minWwidth: 40, height: 40, padding: 14 }}
onPress={() => {
Keyboard.dismiss();
navigation.goBack(null);
}}
>
<Image style={{ alignSelf: 'center' }} source={require('./img/close.png')} />
</TouchableOpacity>
),
headerRight: withAdvancedOptionsMenuButton
? () => (
return {
headerStyle: {
backgroundColor: BlueApp.settings.brandingColor,
borderBottomWidth: 0,
elevation: 0,
shadowOffset: { height: 0, width: 0 },
},
headerTitleStyle: {
fontWeight: '600',
color: BlueApp.settings.foregroundColor,
},
headerTintColor: BlueApp.settings.foregroundColor,
headerRight,
// headerBackTitle: null,
headerBackTitleVisible: false,
};
};
export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuButton = false, advancedOptionsMenuButtonAction) => {
let headerRight;
if (withAdvancedOptionsMenuButton) {
headerRight = () => (
<TouchableOpacity style={{ minWidth: 40, height: 40, padding: 14 }} onPress={advancedOptionsMenuButtonAction}>
<Icon size={22} name="kebab-horizontal" type="octicon" color={BlueApp.settings.foregroundColor} />
</TouchableOpacity>
)
: null,
headerBackTitle: null,
});
<Icon size={22} name="kebab-horizontal" type="octicon" color={BlueApp.settings.foregroundColor} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
headerStyle: {
backgroundColor: BlueApp.settings.brandingColor,
borderBottomWidth: 0,
elevation: 0,
},
headerTitleStyle: {
fontWeight: '600',
color: BlueApp.settings.foregroundColor,
},
headerTintColor: BlueApp.settings.foregroundColor,
headerLeft: () => (
<TouchableOpacity
style={{ minWwidth: 40, height: 40, padding: 14 }}
onPress={() => {
Keyboard.dismiss();
navigation.goBack(null);
}}
>
<Image style={{ alignSelf: 'center' }} source={require('./img/close.png')} />
</TouchableOpacity>
),
headerRight,
headerBackTitle: null,
};
};
export const BluePrivateBalance = () => {
return Platform.select({
@ -1368,8 +1381,7 @@ export class NewWalletPanel extends Component {
render() {
return (
<TouchableOpacity testID="CreateAWallet" {...this.props} onPress={this.props.onPress} style={{ marginVertical: 17 }}>
<LinearGradient
colors={WalletGradient.createWallet}
<View
style={{
paddingHorizontal: 24,
paddingVertical: 16,
@ -1377,6 +1389,7 @@ export class NewWalletPanel extends Component {
minHeight: Platform.OS === 'ios' ? 164 : 181,
justifyContent: 'center',
alignItems: 'flex-start',
backgroundColor: WalletGradient.createWallet,
}}
>
<Text
@ -1409,7 +1422,7 @@ export class NewWalletPanel extends Component {
<View style={{ marginTop: 12, backgroundColor: '#007AFF', paddingHorizontal: 32, paddingVertical: 12, borderRadius: 8 }}>
<Text style={{ color: BlueApp.settings.brandingColor, fontWeight: '500' }}>{loc.wallets.list.create_a_button}</Text>
</View>
</LinearGradient>
</View>
</TouchableOpacity>
);
}
@ -1784,223 +1797,250 @@ export class BlueListTransactionItem extends Component {
}
}
const WalletCarouselItem = ({ item, index, onPress, handleLongPress }) => {
let scaleValue = new Animated.Value(1.0);
const onPressedIn = () => {
let props = { duration: 50 };
if (Platform.OS === 'android') {
props['useNativeDriver'] = true;
}
props.toValue = 0.9;
Animated.spring(scaleValue, props).start();
};
const onPressedOut = () => {
let props = { duration: 50 };
if (Platform.OS === 'android') {
props['useNativeDriver'] = true;
}
props.toValue = 1.0;
Animated.spring(scaleValue, props).start();
};
if (!item) {
return (
<NewWalletPanel
onPress={() => {
onPressedOut();
onPress(index);
onPressedOut();
}}
/>
);
}
if (item.type === PlaceholderWallet.type) {
return (
<Animated.View
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
shadowOpacity={40 / 100}
shadowOffset={{ width: 0, height: 0 }}
shadowRadius={5}
>
<TouchableWithoutFeedback
onPressIn={item.getIsFailure() ? onPressedIn : null}
onPressOut={item.getIsFailure() ? onPressedOut : null}
onPress={() => {
if (item.getIsFailure()) {
onPressedOut();
onPress(index);
onPressedOut();
}
}}
>
<LinearGradient
shadowColor={BlueApp.settings.shadowColor}
colors={WalletGradient.gradientsFor(item.type)}
style={{
padding: 15,
borderRadius: 10,
minHeight: 164,
elevation: 5,
}}
>
<Image
source={require('./img/btc-shape.png')}
style={{
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
}}
/>
<Text style={{ backgroundColor: 'transparent' }} />
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontSize: 19,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{item.getLabel()}
</Text>
{item.getIsFailure() ? (
<Text
numberOfLines={0}
style={{
backgroundColor: 'transparent',
fontSize: 19,
marginTop: 40,
color: BlueApp.settings.inverseForegroundColor,
}}
>
An error was encountered when attempting to import this wallet.
</Text>
) : (
<ActivityIndicator style={{ marginTop: 40 }} />
)}
</LinearGradient>
</TouchableWithoutFeedback>
</Animated.View>
);
} else {
return (
<Animated.View
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
shadowOpacity={40 / 100}
shadowOffset={{ width: 0, height: 0 }}
shadowRadius={5}
>
<TouchableWithoutFeedback
testID={item.getLabel()}
onPressIn={onPressedIn}
onPressOut={onPressedOut}
onLongPress={handleLongPress}
onPress={() => {
onPressedOut();
onPress(index);
onPressedOut();
}}
>
<LinearGradient
shadowColor={BlueApp.settings.shadowColor}
colors={WalletGradient.gradientsFor(item.type)}
style={{
padding: 15,
borderRadius: 10,
minHeight: 164,
elevation: 5,
}}
>
<Image
source={(LightningCustodianWallet.type === item.type && require('./img/lnd-shape.png')) || require('./img/btc-shape.png')}
style={{
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
}}
/>
<Text style={{ backgroundColor: 'transparent' }} />
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontSize: 19,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{item.getLabel()}
</Text>
{item.hideBalance ? (
<BluePrivateBalance />
) : (
<Text
numberOfLines={1}
adjustsFontSizeToFit
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{loc.formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true)}
</Text>
)}
<Text style={{ backgroundColor: 'transparent' }} />
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontSize: 13,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{loc.wallets.list.latest_transaction}
</Text>
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 16,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{loc.transactionTimeToReadable(item.getLatestTransactionTime())}
</Text>
</LinearGradient>
</TouchableWithoutFeedback>
</Animated.View>
);
}
};
const sliderWidth = width * 1;
const itemWidth = width * 0.82;
const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82;
const sliderHeight = 190;
export class WalletsCarousel extends Component {
walletsCarousel = React.createRef();
state = { isLoading: true };
_renderItem = ({ item, index }) => {
let scaleValue = new Animated.Value(1.0);
let props = { duration: 50 };
if (Platform.OS === 'android') {
props['useNativeDriver'] = true;
}
this.onPressedIn = () => {
props.toValue = 0.9;
Animated.spring(scaleValue, props).start();
};
this.onPressedOut = () => {
props.toValue = 1.0;
Animated.spring(scaleValue, props).start();
};
if (!item) {
return (
<NewWalletPanel
onPress={() => {
this.onPressedOut();
this.props.onPress(index);
this.onPressedOut();
}}
/>
);
}
if (item.type === PlaceholderWallet.type) {
return (
<Animated.View
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
shadowOpacity={40 / 100}
shadowOffset={{ width: 0, height: 0 }}
shadowRadius={5}
>
<TouchableWithoutFeedback
onPressIn={item.getIsFailure() ? this.onPressedIn : null}
onPressOut={item.getIsFailure() ? this.onPressedOut : null}
onPress={() => {
if (item.getIsFailure()) {
this.onPressedOut();
this.props.onPress(index);
this.onPressedOut();
}
}}
>
<LinearGradient
shadowColor={BlueApp.settings.shadowColor}
colors={WalletGradient.gradientsFor(item.type)}
style={{
padding: 15,
borderRadius: 10,
minHeight: 164,
elevation: 5,
}}
>
<Image
source={require('./img/btc-shape.png')}
style={{
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
}}
/>
<Text style={{ backgroundColor: 'transparent' }} />
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontSize: 19,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{item.getLabel()}
</Text>
{item.getIsFailure() ? (
<Text
numberOfLines={0}
style={{
backgroundColor: 'transparent',
fontSize: 19,
marginTop: 40,
color: BlueApp.settings.inverseForegroundColor,
}}
>
An error was encountered when attempting to import this wallet.
</Text>
) : (
<ActivityIndicator style={{ marginTop: 40 }} />
)}
</LinearGradient>
</TouchableWithoutFeedback>
</Animated.View>
);
} else {
return (
<Animated.View
style={{ paddingRight: 10, marginVertical: 17, transform: [{ scale: scaleValue }] }}
shadowOpacity={40 / 100}
shadowOffset={{ width: 0, height: 0 }}
shadowRadius={5}
>
<TouchableWithoutFeedback
testID={item.getLabel()}
onPressIn={this.onPressedIn}
onPressOut={this.onPressedOut}
onLongPress={this.props.handleLongPress}
onPress={() => {
this.onPressedOut();
this.props.onPress(index);
this.onPressedOut();
}}
>
<LinearGradient
shadowColor={BlueApp.settings.shadowColor}
colors={WalletGradient.gradientsFor(item.type)}
style={{
padding: 15,
borderRadius: 10,
minHeight: 164,
elevation: 5,
}}
>
<Image
source={(LightningCustodianWallet.type === item.type && require('./img/lnd-shape.png')) || require('./img/btc-shape.png')}
style={{
width: 99,
height: 94,
position: 'absolute',
bottom: 0,
right: 0,
}}
/>
<Text style={{ backgroundColor: 'transparent' }} />
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontSize: 19,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{item.getLabel()}
</Text>
{item.hideBalance ? (
<BluePrivateBalance />
) : (
<Text
numberOfLines={1}
adjustsFontSizeToFit
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 36,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{loc.formatBalance(Number(item.getBalance()), item.getPreferredBalanceUnit(), true)}
</Text>
)}
<Text style={{ backgroundColor: 'transparent' }} />
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontSize: 13,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{loc.wallets.list.latest_transaction}
</Text>
<Text
numberOfLines={1}
style={{
backgroundColor: 'transparent',
fontWeight: 'bold',
fontSize: 16,
color: BlueApp.settings.inverseForegroundColor,
}}
>
{loc.transactionTimeToReadable(item.getLatestTransactionTime())}
</Text>
</LinearGradient>
</TouchableWithoutFeedback>
</Animated.View>
);
}
return <WalletCarouselItem item={item} index={index} handleLongPress={this.props.handleLongPress} onPress={this.props.onPress} />;
};
snapToItem = item => {
this.walletsCarousel.current.snapToItem(item);
};
onLayout = () => {
this.setState({ isLoading: false });
};
render() {
return (
<Carousel
{...this.props}
ref={this.walletsCarousel}
renderItem={this._renderItem}
sliderWidth={sliderWidth}
sliderHeight={sliderHeight}
itemWidth={itemWidth}
inactiveSlideScale={1}
inactiveSlideOpacity={0.7}
contentContainerCustomStyle={{ left: -20 }}
onSnapToItem={this.onSnapToItem}
/>
<>
{this.state.isLoading && (
<View
style={{ paddingVertical: sliderHeight / 2, paddingHorizontal: sliderWidth / 2, position: 'absolute', alignItems: 'center' }}
>
<ActivityIndicator />
</View>
)}
<Carousel
{...this.props}
ref={this.walletsCarousel}
renderItem={this._renderItem}
sliderWidth={sliderWidth}
sliderHeight={sliderHeight}
itemWidth={itemWidth}
inactiveSlideScale={1}
inactiveSlideOpacity={0.7}
initialNumToRender={4}
onLayout={this.onLayout}
contentContainerCustomStyle={{ left: -20 }}
/>
</>
);
}
}

View file

@ -339,7 +339,7 @@ module.exports.multiGetHistoryByAddress = async function(addresses, batchsize) {
};
module.exports.multiGetTransactionByTxid = async function(txids, batchsize, verbose) {
batchsize = batchsize || 49;
batchsize = batchsize || 45;
// this value is fine-tuned so althrough wallets in test suite will occasionally
// throw 'response too large (over 1,000,000 bytes', test suite will pass
verbose = verbose !== false;

View file

@ -39,6 +39,7 @@ import RBFBumpFee from './screen/transactions/RBFBumpFee';
import RBFCancel from './screen/transactions/RBFCancel';
import ReceiveDetails from './screen/receive/details';
import AztecoRedeem from './screen/receive/aztecoRedeem';
import SendDetails from './screen/send/details';
import ScanQRCode from './screen/send/ScanQRCode';
@ -205,6 +206,14 @@ const HandleOffchainAndOnChain = () => (
</HandleOffchainAndOnChainStack.Navigator>
);
const AztecoRedeemStack = createStackNavigator();
const AztecoRedeemRoot = () => (
<AztecoRedeemStack.Navigator>
<AztecoRedeemStack.Screen name="AztecoRedeem" component={AztecoRedeem} options={AztecoRedeem.navigationOptions} />
<AztecoRedeemStack.Screen name="SelectWallet" component={SelectWallet} options={{ headerLeft: null }} />
</AztecoRedeemStack.Navigator>
);
const RootStack = createStackNavigator();
const Navigation = () => (
<RootStack.Navigator mode="modal">
@ -214,7 +223,8 @@ const Navigation = () => (
<RootStack.Screen name="SendDetailsRoot" component={SendDetailsRoot} options={{ headerShown: false }} />
<RootStack.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="HandleOffchainAndOnChain" component={HandleOffchainAndOnChain} />
<RootStack.Screen name="HandleOffchainAndOnChain" component={HandleOffchainAndOnChain} options={{ headerShown: false }} />
<RootStack.Screen name="AztecoRedeemRoot" component={AztecoRedeemRoot} options={{ headerShown: false }} />
{/* screens */}
<RootStack.Screen name="WalletExport" component={WalletExport} options={WalletExport.navigationOptions} />
<RootStack.Screen name="WalletXpub" component={WalletXpub} options={WalletXpub.navigationOptions} />

41
class/azteco.js Normal file
View file

@ -0,0 +1,41 @@
import Frisbee from 'frisbee';
import url from 'url';
export default class Azteco {
/**
* Redeems an Azteco bitcoin voucher.
*
* @param {string[]} voucher - 16-digit voucher code in groups of 4.
* @param {string} address - Bitcoin address to send the redeemed bitcoin to.
*
* @returns {Promise<boolean>} Successfully redeemed or not. This method does not throw exceptions
*/
static async redeem(voucher, address) {
const api = new Frisbee({
baseURI: 'https://azte.co/',
});
const url = `/blue_despatch.php?CODE_1=${voucher[0]}&CODE_2=${voucher[1]}&CODE_3=${voucher[2]}&CODE_4=${voucher[3]}&ADDRESS=${address}`;
try {
let response = await api.get(url);
return response && response.originalResponse && +response.originalResponse.status === 200;
} catch (_) {
return false;
}
}
static isRedeemUrl(u) {
return u.startsWith('https://azte.co');
}
static getParamsFromUrl(u) {
let urlObject = url.parse(u, true); // eslint-disable-line
return {
uri: u,
c1: urlObject.query.c1,
c2: urlObject.query.c2,
c3: urlObject.query.c3,
c4: urlObject.query.c4,
};
}
}

View file

@ -4,6 +4,7 @@ import BitcoinBIP70TransactionDecode from '../bip70/bip70';
import RNFS from 'react-native-fs';
import url from 'url';
import { Chain } from '../models/bitcoinUnits';
import Azteco from './azteco';
const bitcoin = require('bitcoinjs-lib');
const bip21 = require('bip21');
const BlueApp: AppStorage = require('../BlueApp');
@ -27,7 +28,7 @@ class DeeplinkSchemaMatch {
* navigation dictionary required by react-navigation
*
* @param event {{url: string}} URL deeplink as passed to app, e.g. `bitcoin:bc1qh6tf004ty7z7un2v5ntu4mkf630545gvhs45u7?amount=666&label=Yo`
* @param completionHandler {function} Returns [string, params: object]
* @param completionHandler {function} Callback that returns [string, params: object]
*/
static navigationRouteFor(event, completionHandler) {
if (event.url === null) {
@ -118,9 +119,17 @@ class DeeplinkSchemaMatch {
safelloStateToken,
},
]);
} else if (Azteco.isRedeemUrl(event.url)) {
completionHandler([
'AztecoRedeemRoot',
{
screen: 'AztecoRedeem',
params: Azteco.getParamsFromUrl(event.url),
},
]);
} else {
let urlObject = url.parse(event.url, true); // eslint-disable-line
console.log('parsed', urlObject);
console.log('parsed', event.url, 'into', urlObject);
(async () => {
if (urlObject.protocol === 'bluewallet:' || urlObject.protocol === 'lapp:' || urlObject.protocol === 'blue:') {
switch (urlObject.host) {

View file

@ -20,7 +20,7 @@ export default class WalletGradient {
static hdLegacyBreadWallet = ['#fe6381', '#f99c42'];
static defaultGradients = ['#c65afb', '#9053fe'];
static lightningCustodianWallet = ['#f1be07', '#f79056'];
static createWallet = ['#eef0f4', '#eef0f4'];
static createWallet = '#eef0f4';
static gradientsFor(type) {
let gradient;
@ -54,9 +54,6 @@ export default class WalletGradient {
case SegwitBech32Wallet.type:
gradient = WalletGradient.segwitBech32Wallet;
break;
case 'CreateWallet':
gradient = WalletGradient.createWallet;
break;
default:
gradient = WalletGradient.defaultGradients;
break;
@ -93,9 +90,6 @@ export default class WalletGradient {
case LightningCustodianWallet.type:
gradient = WalletGradient.lightningCustodianWallet;
break;
case 'CreateWallet':
gradient = WalletGradient.createWallet;
break;
default:
gradient = WalletGradient.defaultGradients;
break;

View file

@ -196,13 +196,13 @@ PODS:
- React
- react-native-blur (0.8.0):
- React
- react-native-camera (3.23.1):
- react-native-camera (3.26.0):
- React
- react-native-camera/RCT (= 3.23.1)
- react-native-camera/RN (= 3.23.1)
- react-native-camera/RCT (3.23.1):
- react-native-camera/RCT (= 3.26.0)
- react-native-camera/RN (= 3.26.0)
- react-native-camera/RCT (3.26.0):
- React
- react-native-camera/RN (3.23.1):
- react-native-camera/RN (3.26.0):
- React
- react-native-document-picker (3.2.0):
- React
@ -281,7 +281,7 @@ PODS:
- React
- RNSecureKeyStore (1.0.0):
- React
- RNSentry (1.3.5):
- RNSentry (1.3.7):
- React
- Sentry (~> 4.4.0)
- RNShare (2.0.0):
@ -513,7 +513,7 @@ SPEC CHECKSUMS:
react-native-biometrics: c892904948a32295b128f633bcc11eda020645c5
react-native-blue-crypto: 23f1558ad3d38d7a2edb7e2f6ed1bc520ed93e56
react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-camera: 6fe72fd0a85732e2449928f9c59a2e0bf661ad3b
react-native-camera: 9e3d60336e221d62e5b72adf551ca31ff74a94b5
react-native-document-picker: e3516aff0dcf65ee0785d9bcf190eb10e2261154
react-native-image-picker: 3637d63fef7e32a230141ab4660d3ceb773c824f
react-native-randombytes: 991545e6eaaf700b4ee384c291ef3d572e0b2ca8
@ -545,7 +545,7 @@ SPEC CHECKSUMS:
RNReactNativeHapticFeedback: 2566b468cc8d0e7bb2f84b23adc0f4614594d071
RNScreens: 62211832af51e0aebcf6e8c36bcf7dd65592f244
RNSecureKeyStore: f1ad870e53806453039f650720d2845c678d89c8
RNSentry: 8a47c89b65502d8d5b464dfb3aa5a13038e5bb08
RNSentry: 370623102247ebea20191b5fb549ecc7a275b324
RNShare: 8b171d4b43c1d886917fdd303bf7a4b87167b05c
RNSVG: 8ba35cbeb385a52fd960fd28db9d7d18b4c2974f
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4

View file

@ -67,9 +67,9 @@ module.exports = {
export_backup: 'Exportieren / Backup',
buy_bitcoin: 'Bitcoin kaufen',
show_xpub: 'Wallet XPUB zeigen',
connected_to: 'Connected to',
advanced: 'Advanced',
use_with_hardware_wallet: 'Use with hardware wallet',
connected_to: 'Verbunden mit',
advanced: 'Fortgeschritten',
use_with_hardware_wallet: 'Hardware Wallet nutzen',
},
export: {
title: 'Wallet exportieren',

263
package-lock.json generated
View file

@ -1813,9 +1813,12 @@
}
},
"@react-native-community/async-storage": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.8.1.tgz",
"integrity": "sha512-MA1fTp4SB7OOtDmNAwds6jIpiwwty1NIoFboWjEWkoyWW35zIuxlhHxD4joSy21aWEzUVwvv6JJ2hSsP/HTb7A=="
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.10.0.tgz",
"integrity": "sha512-kPJwhUpBKLXGrBnUjx0JVSJvSEl5nPO+puJ3Uy9pMvika9uWeniRGZPQjUWWQimU5M7xhQ41d5I1OP82Q3Xx9A==",
"requires": {
"deep-assign": "^3.0.0"
}
},
"@react-native-community/blur": {
"version": "3.6.0",
@ -2032,27 +2035,26 @@
"from": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git"
},
"@sentry/browser": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.15.4.tgz",
"integrity": "sha512-l/auT1HtZM3KxjCGQHYO/K51ygnlcuOrM+7Ga8gUUbU9ZXDYw6jRi0+Af9aqXKmdDw1naNxr7OCSy6NBrLWVZw==",
"version": "5.15.5",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.15.5.tgz",
"integrity": "sha512-rqDvjk/EvogfdbZ4TiEpxM/lwpPKmq23z9YKEO4q81+1SwJNua53H60dOk9HpRU8nOJ1g84TMKT2Ov8H7sqDWA==",
"requires": {
"@sentry/core": "5.15.4",
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"@sentry/core": "5.15.5",
"@sentry/types": "5.15.5",
"@sentry/utils": "5.15.5",
"tslib": "^1.9.3"
}
},
"@sentry/cli": {
"version": "1.52.1",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.52.1.tgz",
"integrity": "sha512-XocAy3opa7bxWEbYQ9R/whbIb4BAX2YHXvfMoCwZRzLRy9cf85FYGQCMi8JA7wQd5PBmcxUh31AxcX7jAfMPCQ==",
"version": "1.53.0",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.53.0.tgz",
"integrity": "sha512-FgVR+AqPd1elj/HGTCg4FcQDVmIGwKGtaHDzHi2ipph9EOVYm6Ce0xYcHxYgKZuVyQMyg+zD5ZK3yHrB1AYlnw==",
"requires": {
"fs-copy-file-sync": "^1.1.1",
"https-proxy-agent": "^4.0.0",
"mkdirp": "^0.5.4",
"node-fetch": "^2.1.2",
"progress": "2.0.0",
"proxy-from-env": "^1.0.0"
"https-proxy-agent": "^5.0.0",
"mkdirp": "^0.5.5",
"node-fetch": "^2.6.0",
"progress": "^2.0.3",
"proxy-from-env": "^1.1.0"
},
"dependencies": {
"mkdirp": {
@ -2062,84 +2064,89 @@
"requires": {
"minimist": "^1.2.5"
}
},
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
}
}
},
"@sentry/core": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.15.4.tgz",
"integrity": "sha512-9KP4NM4SqfV5NixpvAymC7Nvp36Zj4dU2fowmxiq7OIbzTxGXDhwuN/t0Uh8xiqlkpkQqSECZ1OjSFXrBldetQ==",
"version": "5.15.5",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.15.5.tgz",
"integrity": "sha512-enxBLv5eibBMqcWyr+vApqeix8uqkfn0iGsD3piKvoMXCgKsrfMwlb/qo9Ox0lKr71qIlZVt+9/A2vZohdgnlg==",
"requires": {
"@sentry/hub": "5.15.4",
"@sentry/minimal": "5.15.4",
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"@sentry/hub": "5.15.5",
"@sentry/minimal": "5.15.5",
"@sentry/types": "5.15.5",
"@sentry/utils": "5.15.5",
"tslib": "^1.9.3"
}
},
"@sentry/hub": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.15.4.tgz",
"integrity": "sha512-1XJ1SVqadkbUT4zLS0TVIVl99si7oHizLmghR8LMFl5wOkGEgehHSoOydQkIAX2C7sJmaF5TZ47ORBHgkqclUg==",
"version": "5.15.5",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.15.5.tgz",
"integrity": "sha512-zX9o49PcNIVMA4BZHe//GkbQ4Jx+nVofqU/Il32/IbwKhcpPlhGX3c1sOVQo4uag3cqd/JuQsk+DML9TKkN0Lw==",
"requires": {
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"@sentry/types": "5.15.5",
"@sentry/utils": "5.15.5",
"tslib": "^1.9.3"
}
},
"@sentry/integrations": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.15.4.tgz",
"integrity": "sha512-GaEVQf4R+WBJvTOGptOHIFSylnH1JAvBQZ7c45jGIDBp+upqzeI67KD+HoM4sSNT2Y2i8DLTJCWibe34knz5Kw==",
"version": "5.15.5",
"resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.15.5.tgz",
"integrity": "sha512-s9N9altnGkDH+vNNUZu1dKuMVLAgJNYtgs6DMJTrZRswFl8gzZytYTZCdpzjBgTsqkLaGbRDIjQeE/yP3gnrqw==",
"requires": {
"@sentry/types": "5.15.4",
"@sentry/utils": "5.15.4",
"@sentry/types": "5.15.5",
"@sentry/utils": "5.15.5",
"tslib": "^1.9.3"
}
},
"@sentry/minimal": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.15.4.tgz",
"integrity": "sha512-GL4GZ3drS9ge+wmxkHBAMEwulaE7DMvAEfKQPDAjg2p3MfcCMhAYfuY4jJByAC9rg9OwBGGehz7UmhWMFjE0tw==",
"version": "5.15.5",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.15.5.tgz",
"integrity": "sha512-zQkkJ1l9AjmU/Us5IrOTzu7bic4sTPKCatptXvLSTfyKW7N6K9MPIIFeSpZf9o1yM2sRYdK7GV08wS2eCT3JYw==",
"requires": {
"@sentry/hub": "5.15.4",
"@sentry/types": "5.15.4",
"@sentry/hub": "5.15.5",
"@sentry/types": "5.15.5",
"tslib": "^1.9.3"
}
},
"@sentry/react-native": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-1.3.5.tgz",
"integrity": "sha512-shgh3Nq9f43CVvrFgb8wBr5sdOWX0T2j0GQ2h1+JR4YvB7vGjIq1xCEZWql4GeAiImnzg15JNnsLAsgOWxF84A==",
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/@sentry/react-native/-/react-native-1.3.7.tgz",
"integrity": "sha512-Nv19bQOimEU4v8tvaiRD4M9mhbiU4HL8JJypA84HCYAG3CeBIQAwrA0lHIgk08ieBz7VKYzOJHBzOANDw3mAjg==",
"requires": {
"@sentry/browser": "^5.15.2",
"@sentry/core": "^5.15.2",
"@sentry/integrations": "^5.15.2",
"@sentry/types": "^5.15.2",
"@sentry/utils": "^5.15.2",
"@sentry/browser": "^5.15.4",
"@sentry/core": "^5.15.4",
"@sentry/integrations": "^5.15.4",
"@sentry/types": "^5.15.4",
"@sentry/utils": "^5.15.4",
"@sentry/wizard": "^1.1.1"
}
},
"@sentry/types": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.15.4.tgz",
"integrity": "sha512-quPHPpeAuwID48HLPmqBiyXE3xEiZLZ5D3CEbU3c3YuvvAg8qmfOOTI6z4Z3Eedi7flvYpnx3n7N3dXIEz30Eg=="
"version": "5.15.5",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.15.5.tgz",
"integrity": "sha512-F9A5W7ucgQLJUG4LXw1ZIy4iLevrYZzbeZ7GJ09aMlmXH9PqGThm1t5LSZlVpZvUfQ2rYA8NU6BdKJSt7B5LPw=="
},
"@sentry/utils": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.15.4.tgz",
"integrity": "sha512-lO8SLBjrUDGADl0LOkd55R5oL510d/1SaI08/IBHZCxCUwI4TiYo5EPECq8mrj3XGfgCyq9osw33bymRlIDuSQ==",
"version": "5.15.5",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.15.5.tgz",
"integrity": "sha512-Nl9gl/MGnzSkuKeo3QaefoD/OJrFLB8HmwQ7HUbTXb6E7yyEzNKAQMHXGkwNAjbdYyYbd42iABP6Y5F/h39NtA==",
"requires": {
"@sentry/types": "5.15.4",
"@sentry/types": "5.15.5",
"tslib": "^1.9.3"
}
},
"@sentry/wizard": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@sentry/wizard/-/wizard-1.1.2.tgz",
"integrity": "sha512-z7Ck5uli91omT+xSGzOXA3XNj0IUFritzZ5Qjf/KcuSUZuyqLCH2olAR6pXl262tC6kBbWw/xb+AOgPsAQ7u/Q==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@sentry/wizard/-/wizard-1.1.4.tgz",
"integrity": "sha512-xVpL0lnQK2bbEwUKKjs3dKhy27va8HW75Q8r1vaR63iBCpB5LpP4Q4NN5G/VEWdYnH8rcazsOA207716E1cm4g==",
"requires": {
"@sentry/cli": "^1.51.0",
"@sentry/cli": "^1.52.4",
"chalk": "^2.4.1",
"glob": "^7.1.3",
"inquirer": "^6.2.0",
@ -2381,6 +2388,11 @@
"event-target-shim": "^5.0.0"
}
},
"abortcontroller-polyfill": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz",
"integrity": "sha512-3ZFfCRfDzx3GFjO6RAkYx81lPGpUS20ISxux9gLxuKnqafNcFQo59+IoZqpO2WvQlyc287B62HDnDdNYRmlvWA=="
},
"absolute-path": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz",
@ -2447,9 +2459,12 @@
"dev": true
},
"agent-base": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g=="
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz",
"integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==",
"requires": {
"debug": "4"
}
},
"ajv": {
"version": "6.12.0",
@ -3853,6 +3868,11 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"boolean": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz",
"integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA=="
},
"boolify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/boolify/-/boolify-1.0.1.tgz",
@ -3983,17 +4003,26 @@
}
},
"browserify-sign": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
"integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.0.tgz",
"integrity": "sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==",
"requires": {
"bn.js": "^4.1.1",
"browserify-rsa": "^4.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.2",
"elliptic": "^6.0.0",
"inherits": "^2.0.1",
"parse-asn1": "^5.0.0"
"bn.js": "^5.1.1",
"browserify-rsa": "^4.0.1",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
"elliptic": "^6.5.2",
"inherits": "^2.0.4",
"parse-asn1": "^5.1.5",
"readable-stream": "^3.6.0",
"safe-buffer": "^5.2.0"
},
"dependencies": {
"bn.js": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.2.tgz",
"integrity": "sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA=="
}
}
},
"browserify-zlib": {
@ -4938,6 +4967,14 @@
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.6.0.tgz",
"integrity": "sha1-Dm2o8M5Sg471zsXI+TlrDBtko8s="
},
"deep-assign": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz",
"integrity": "sha512-YX2i9XjJ7h5q/aQ/IM9PEwEnDqETAIYbggmdDB3HLTlSgo1CxPsj6pvhPG68rq6SVE0+p+6Ywsm5fTYNrYtBWw==",
"requires": {
"is-obj": "^1.0.0"
}
},
"deep-equal": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
@ -5070,9 +5107,9 @@
"dev": true
},
"detox": {
"version": "16.4.0",
"resolved": "https://registry.npmjs.org/detox/-/detox-16.4.0.tgz",
"integrity": "sha512-2dB6bJ9GCM7BuZCWubSRQLX1QLaKvPue9zu4+jcDMhhbQL08KKIClb2yelMalSyjtM04hX7OUw+SBArIwU13Dg==",
"version": "16.6.0",
"resolved": "https://registry.npmjs.org/detox/-/detox-16.6.0.tgz",
"integrity": "sha512-2FziZMJ2+fEEwNCR1kfZ/e/CqTFIJXC1jRHdyK1t3oePfT1Qq+V22ejdfxs061b1o9AsoJrjPqi+It8AOaQCsw==",
"dev": true,
"requires": {
"@babel/core": "^7.4.5",
@ -6757,22 +6794,22 @@
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"frisbee": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/frisbee/-/frisbee-2.0.9.tgz",
"integrity": "sha512-k2YrtBki5iw0p2Wsee5KW0cQBpzvDAOcAXMcunNCwM31XCj4RFpa1WGA4541JY9wFz8QhTHBQXb2rE3ERvbr9w==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/frisbee/-/frisbee-3.1.2.tgz",
"integrity": "sha512-qkhtfxUsTFS2BfWJcSKXWAMYPq9RhJkcrikOGPxvzhklI+RAM8tWd1V7n3t6V1uwPNjfPegNG5q1r5Yp3zXOFw==",
"requires": {
"@babel/runtime": "^7.7.4",
"abortcontroller-polyfill": "^1.4.0",
"boolean": "^3.0.0",
"caseless": "^0.12.0",
"common-tags": "^1.8.0",
"cross-fetch": "^3.0.2",
"qs": "^6.7.0",
"url-join": "^4.0.0"
"cross-fetch": "^3.0.4",
"debug": "^4.1.1",
"qs": "6.9.1",
"url-join": "^4.0.1",
"url-parse": "^1.4.7"
}
},
"fs-copy-file-sync": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/fs-copy-file-sync/-/fs-copy-file-sync-1.1.1.tgz",
"integrity": "sha512-2QY5eeqVv4m2PfyMiEuy9adxNP+ajf+8AR05cEi+OAzPcOj90hvFImeZhTmKLBgSd9EvG33jsD7ZRxsx9dThkQ=="
},
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
@ -7670,11 +7707,11 @@
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
},
"https-proxy-agent": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz",
"integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
"requires": {
"agent-base": "5",
"agent-base": "6",
"debug": "4"
}
},
@ -8001,6 +8038,11 @@
}
}
},
"is-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
},
"is-object": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/is-object/-/is-object-0.1.2.tgz",
@ -10288,9 +10330,9 @@
}
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
"version": "2.26.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz",
"integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==",
"dev": true,
"optional": true
},
@ -10436,11 +10478,12 @@
"integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs="
},
"node-libs-react-native": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/node-libs-react-native/-/node-libs-react-native-1.0.3.tgz",
"integrity": "sha512-2X/M/DMB4hij2L0tsnJOiDhYR2N0YtetIhb/eN5+5vksLxjXwaFgLbSXWT3XExnGJpISDn8dYuYz6yvdndjjkg==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/node-libs-react-native/-/node-libs-react-native-1.2.0.tgz",
"integrity": "sha512-D+g8Mj9OfsOYYFWZoSz4bmr/8g/QWOwUgDmi3Ux0EPU9Q41OvGsdzAzr0IGJmtuSAfyOTfnKx1XRQR9id8/EEw==",
"requires": {
"assert": "^1.4.1",
"base-64": "^0.1.0",
"browserify-zlib": "^0.2.0",
"buffer": "^5.0.6",
"console-browserify": "^1.1.0",
@ -11762,7 +11805,8 @@
"progress": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz",
"integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8="
"integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=",
"dev": true
},
"promise": {
"version": "7.3.1",
@ -11977,6 +12021,11 @@
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
},
"querystringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
},
"quick-lru": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
@ -12386,9 +12435,9 @@
"from": "git+https://github.com/Overtorment/react-native-blue-crypto.git"
},
"react-native-camera": {
"version": "3.23.1",
"resolved": "https://registry.npmjs.org/react-native-camera/-/react-native-camera-3.23.1.tgz",
"integrity": "sha512-ghQT2IhiZiNMDgixD/MrfwgFx9arwOb2z79xDJ8dsz8DFcpBMbXjXZETaAK0WaZ/MWOaY84k0eGlx1hoVT77wQ==",
"version": "3.26.0",
"resolved": "https://registry.npmjs.org/react-native-camera/-/react-native-camera-3.26.0.tgz",
"integrity": "sha512-W/h89LN+jujlzc89nWpvukbfnbFO+Fskf6PR23pP6zminpJDIArHabWLd1mQoJ3p6r+gUJ8I4bgsxUmSMgLAgA==",
"requires": {
"prop-types": "^15.6.2"
}
@ -13183,6 +13232,11 @@
"integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
"dev": true
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"resolve": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
@ -14652,6 +14706,15 @@
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
},
"url-parse": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",

View file

@ -10,7 +10,7 @@
"babel-eslint": "^10.1.0",
"babel-jest": "^26.0.1",
"babel-preset-flow": "^6.23.0",
"detox": "^16.4.0",
"detox": "16.6.0",
"eslint": "^6.5.1",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-import": "^2.18.0",
@ -63,14 +63,14 @@
},
"dependencies": {
"@babel/preset-env": "7.9.6",
"@react-native-community/async-storage": "1.8.1",
"@react-native-community/async-storage": "1.10.0",
"@react-native-community/blur": "3.6.0",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/slider": "2.0.8",
"@react-navigation/native": "5.4.3",
"@react-navigation/stack": "5.4.0",
"@remobile/react-native-qrcode-local-image": "git+https://github.com/BlueWallet/react-native-qrcode-local-image.git",
"@sentry/react-native": "1.3.5",
"@sentry/react-native": "1.3.7",
"amplitude-js": "5.9.0",
"bech32": "1.1.3",
"bignumber.js": "9.0.0",
@ -93,10 +93,10 @@
"eslint-plugin-prettier": "3.1.2",
"eslint-plugin-standard": "4.0.0",
"events": "1.1.1",
"frisbee": "2.0.9",
"frisbee": "3.1.2",
"intl": "1.2.5",
"lottie-react-native": "3.1.1",
"node-libs-react-native": "1.0.3",
"node-libs-react-native": "1.2.0",
"path-browserify": "1.0.0",
"pbkdf2": "3.0.17",
"prettier": "1.19.1",
@ -107,7 +107,7 @@
"react-native": "0.61.5",
"react-native-biometrics": "git+https://github.com/BlueWallet/react-native-biometrics.git#2.0.0",
"react-native-blue-crypto": "git+https://github.com/Overtorment/react-native-blue-crypto.git",
"react-native-camera": "3.23.1",
"react-native-camera": "3.26.0",
"react-native-default-preference": "1.4.1",
"react-native-device-info": "4.0.1",
"react-native-document-picker": "git+https://github.com/BlueWallet/react-native-document-picker.git#9ce83792db340d01b1361d24b19613658abef4aa",

View file

@ -0,0 +1,148 @@
/* global alert */
import React, { Component } from 'react';
import { Keyboard, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
import { Icon } from 'react-native-elements';
import { BlueButton, BlueCreateTxNavigationStyle, BlueLoading, BlueSpacing, BlueText } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { AppStorage, PlaceholderWallet } from '../../class';
import Azteco from '../../class/azteco';
const EV = require('../../events');
let BlueApp: AppStorage = require('../../BlueApp');
export default class AztecoRedeem extends Component {
static navigationOptions = ({ navigation }) => ({
...BlueCreateTxNavigationStyle(navigation),
title: 'Redeem Azte.co voucher',
});
state = { isLoading: true };
constructor(props) {
super(props);
/** @type {AbstractWallet} */
let toWallet = null;
const wallets = BlueApp.getWallets().filter(wallet => wallet.type !== PlaceholderWallet.type);
if (wallets.length === 0) {
alert('Before redeeming you must first add a Bitcoin wallet.');
return props.navigation.goBack(null);
} else {
if (wallets.length > 0) {
toWallet = wallets[0];
}
this.state = {
c1: props.route.params.c1,
c2: props.route.params.c2,
c3: props.route.params.c3,
c4: props.route.params.c4,
isLoading: false,
toWallet,
renderWalletSelectionButtonHidden: false,
};
}
}
async componentDidMount() {
console.log('AztecoRedeem - componentDidMount');
}
onWalletSelect = toWallet => {
this.setState({ toWallet }, () => {
this.props.navigation.pop();
});
};
redeem = async () => {
this.setState({ isLoading: true });
const address = await this.state.toWallet.getAddressAsync();
const result = await Azteco.redeem([this.state.c1, this.state.c2, this.state.c3, this.state.c4], address);
if (!result) {
alert('Something went wrong. Is this voucher still valid?');
this.setState({ isLoading: false });
} else {
this.props.navigation.pop();
// remote because we want to refetch from server tx list and balance
setTimeout(() => EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED), 4000);
alert('Success');
}
};
renderWalletSelectionButton = () => {
if (this.state.renderWalletSelectionButtonHidden) return;
return (
<View style={{ marginBottom: 24, alignItems: 'center' }}>
{!this.state.isLoading && (
<TouchableOpacity
style={{ flexDirection: 'row', alignItems: 'center' }}
onPress={() =>
this.props.navigation.navigate('SelectWallet', {
onWalletSelect: this.onWalletSelect,
availableWallets: BlueApp.getWallets(),
})
}
>
<Text style={{ color: '#9aa0aa', fontSize: 14, marginRight: 8 }}>Redeem to wallet</Text>
<Icon name="angle-right" size={18} type="font-awesome" color="#9aa0aa" />
</TouchableOpacity>
)}
<View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 4 }}>
<TouchableOpacity
style={{ flexDirection: 'row', alignItems: 'center' }}
onPress={() =>
this.props.navigation.navigate('SelectWallet', {
onWalletSelect: this.onWalletSelect,
availableWallets: BlueApp.getWallets(),
})
}
>
<Text style={{ color: '#0c2550', fontSize: 14 }}>{this.state.toWallet.getLabel()}</Text>
</TouchableOpacity>
</View>
</View>
);
};
render() {
if (this.state.isLoading || typeof this.state.toWallet === 'undefined') {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<BlueLoading />
</View>
);
}
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<View>
<View style={{ alignItems: 'center', alignContent: 'flex-end', marginTop: 66 }}>
<Text>Your voucher code is</Text>
<BlueText style={{ color: '#0c2550', fontSize: 20, marginTop: 20, marginBottom: 90 }}>
{this.state.c1}-{this.state.c2}-{this.state.c3}-{this.state.c4}
</BlueText>
{this.renderWalletSelectionButton()}
<BlueButton onPress={this.redeem} title={'Redeem'} />
<BlueSpacing />
</View>
</View>
</TouchableWithoutFeedback>
);
}
}
AztecoRedeem.propTypes = {
navigation: PropTypes.shape({
pop: PropTypes.func,
goBack: PropTypes.func,
navigate: PropTypes.func,
}),
route: PropTypes.shape({
params: PropTypes.shape({
c1: PropTypes.string,
c2: PropTypes.string,
c3: PropTypes.string,
c4: PropTypes.string,
}),
}),
};

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState, useCallback } from 'react';
import { View, InteractionManager, Platform, TextInput, KeyboardAvoidingView, Keyboard, StyleSheet, ScrollView } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { useNavigation, useRoute } from '@react-navigation/native';
import { useNavigation, useRoute, useIsFocused } from '@react-navigation/native';
import {
BlueLoading,
SafeBlueArea,
@ -38,6 +38,7 @@ const ReceiveDetails = () => {
const [isCustom, setIsCustom] = useState(false);
const [isCustomModalVisible, setIsCustomModalVisible] = useState(false);
const { navigate, goBack } = useNavigation();
const isFocused = useIsFocused();
const renderReceiveDetails = useCallback(async () => {
console.log('receive/details - componentDidMount');
@ -210,7 +211,7 @@ const ReceiveDetails = () => {
</BlueText>
</>
)}
{bip21encoded === undefined ? (
{bip21encoded === undefined && isFocused ? (
<View style={{ alignItems: 'center', width: 300, height: 300 }}>
<BlueLoading />
</View>

View file

@ -4,20 +4,20 @@ import { Image, View, TouchableOpacity, Platform } from 'react-native';
import { RNCamera } from 'react-native-camera';
import { Icon } from 'react-native-elements';
import ImagePicker from 'react-native-image-picker';
import PropTypes from 'prop-types';
import { useNavigation, useRoute } from '@react-navigation/native';
import { useNavigation, useRoute, useIsFocused } from '@react-navigation/native';
import DocumentPicker from 'react-native-document-picker';
import RNFS from 'react-native-fs';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const createHash = require('create-hash');
const ScanQRCode = ({ showCloseButton = true, showFileImportButton: showFileImportButtonProps }) => {
const ScanQRCode = () => {
const [isLoading, setIsLoading] = useState(false);
const { navigate } = useNavigation();
const route = useRoute();
const showFileImportButton = showFileImportButtonProps || route.params.showFileImportButton || false;
const showFileImportButton = route.params.showFileImportButton || false;
const { launchedBy, onBarScanned } = route.params;
const scannedCache = {};
const isFocused = useIsFocused();
const HashIt = function(s) {
return createHash('sha256')
@ -37,7 +37,7 @@ const ScanQRCode = ({ showCloseButton = true, showFileImportButton: showFileImpo
if (!isLoading) {
setIsLoading(true);
try {
if (showCloseButton && launchedBy) {
if (launchedBy) {
navigate(launchedBy);
}
if (ret.additionalProperties) {
@ -76,9 +76,39 @@ const ScanQRCode = ({ showCloseButton = true, showFileImportButton: showFileImpo
setIsLoading(false);
};
const showImagePicker = () => {
if (!isLoading) {
setIsLoading(true);
ImagePicker.launchImageLibrary(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarCodeRead({ data: result });
} else {
alert('The selected image does not contain a QR Code.');
}
});
}
setIsLoading(false);
},
);
}
};
const dismiss = () => {
navigate(launchedBy);
};
return (
<View style={{ flex: 1, backgroundColor: '#000000' }}>
{!isLoading && (
{!isLoading && isFocused && (
<RNCamera
captureAudio={false}
androidCameraPermissionOptions={{
@ -87,28 +117,26 @@ const ScanQRCode = ({ showCloseButton = true, showFileImportButton: showFileImpo
buttonPositive: 'OK',
buttonNegative: 'Cancel',
}}
style={{ flex: 1, justifyContent: 'space-between', backgroundColor: '#000000' }}
style={{ flex: 1 }}
onBarCodeRead={onBarCodeRead}
barCodeTypes={[RNCamera.Constants.BarCodeType.qr]}
/>
)}
{showCloseButton && (
<TouchableOpacity
style={{
width: 40,
height: 40,
backgroundColor: 'rgba(0,0,0,0.4)',
justifyContent: 'center',
borderRadius: 20,
position: 'absolute',
right: 16,
top: 44,
}}
onPress={() => navigate(launchedBy)}
>
<Image style={{ alignSelf: 'center' }} source={require('../../img/close-white.png')} />
</TouchableOpacity>
)}
<TouchableOpacity
style={{
width: 40,
height: 40,
backgroundColor: 'rgba(0,0,0,0.4)',
justifyContent: 'center',
borderRadius: 20,
position: 'absolute',
right: 16,
top: 44,
}}
onPress={dismiss}
>
<Image style={{ alignSelf: 'center' }} source={require('../../img/close-white.png')} />
</TouchableOpacity>
<TouchableOpacity
style={{
width: 40,
@ -120,31 +148,7 @@ const ScanQRCode = ({ showCloseButton = true, showFileImportButton: showFileImpo
left: 24,
bottom: 48,
}}
onPress={() => {
if (!isLoading) {
setIsLoading(true);
ImagePicker.launchImageLibrary(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarCodeRead({ data: result });
} else {
alert('The selected image does not contain a QR Code.');
}
});
}
setIsLoading(false);
},
);
}
}}
onPress={showImagePicker}
>
<Icon name="image" type="font-awesome" color="#ffffff" />
</TouchableOpacity>
@ -172,8 +176,5 @@ const ScanQRCode = ({ showCloseButton = true, showFileImportButton: showFileImpo
ScanQRCode.navigationOptions = {
headerShown: false,
};
ScanQRCode.propTypes = {
showFileImportButton: PropTypes.bool,
showCloseButton: PropTypes.bool,
};
export default ScanQRCode;

View file

@ -28,17 +28,24 @@ const loc = require('../../loc');
const currency = require('../../currency');
export default class SendCreate extends Component {
static navigationOptions = ({ navigation, route }) => ({
...BlueNavigationStyle,
title: loc.send.create.details,
headerRight: route.params.exportTXN
? () => (
static navigationOptions = ({ navigation, route }) => {
let headerRight;
if (route.params.exportTXN) {
headerRight = () => (
<TouchableOpacity style={{ marginRight: 16 }} onPress={route.params.exportTXN}>
<Icon size={22} name="share-alternative" type="entypo" color={BlueApp.settings.foregroundColor} />
</TouchableOpacity>
)
: null,
});
<Icon size={22} name="share-alternative" type="entypo" color={BlueApp.settings.foregroundColor} />
</TouchableOpacity>
);
} else {
headerRight = null;
}
return {
...BlueNavigationStyle,
title: loc.send.create.details,
headerRight,
};
};
constructor(props) {
super(props);

View file

@ -17,9 +17,14 @@ const NetworkSettings = () => {
) : (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={{ flex: 1 }}>
<ScrollView>
<BlueListItem title={'Electrum server'} component={TouchableOpacity} onPress={() => navigate('ElectrumSettings')} />
<BlueListItem title={loc.settings.lightning_settings} component={TouchableOpacity} onPress={() => navigate('LightningSettings')} />
<BlueListItem title="Broadcast transaction" component={TouchableOpacity} onPress={() => navigate('Broadcast')} />
<BlueListItem title={'Electrum server'} component={TouchableOpacity} onPress={() => navigate('ElectrumSettings')} chevron />
<BlueListItem
title={loc.settings.lightning_settings}
component={TouchableOpacity}
onPress={() => navigate('LightningSettings')}
chevron
/>
<BlueListItem title="Broadcast transaction" component={TouchableOpacity} onPress={() => navigate('Broadcast')} chevron />
</ScrollView>
</SafeBlueArea>
);

View file

@ -85,7 +85,7 @@ const About = () => {
<Text style={{ maxWidth: 260, marginBottom: 40, color: '#0C2550', fontSize: 15, textAlign: 'center', fontWeight: '500' }}>
Always backup your keys!
</Text>
<BlueButton onPress={handleOnRatePress} title="help with a review ⭐🙏" />
<BlueButton onPress={handleOnRatePress} title="Leave us a review ⭐🙏" />
</View>
</BlueCard>
<BlueListItem

View file

@ -42,7 +42,7 @@ const Currency = () => {
disabled={isSavingNewPreferredCurrency}
title={`${item.endPointKey} (${item.symbol})`}
{...(selectedCurrency.endPointKey === item.endPointKey
? { rightIcon: <Icon name="check" type="font-awesome" color="#0c2550" /> }
? { rightIcon: <Icon name="check" type="octaicon" color="#0070FF" /> }
: { hideChevron: true })}
Component={TouchableOpacity}
onPress={async () => {

View file

@ -65,7 +65,7 @@ export default class Language extends Component {
title={item.label}
{...(this.state.language === item.value
? {
rightIcon: <Icon name="check" type="font-awesome" color="#0c2550" />,
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />,
}
: { hideChevron: true })}
/>

View file

@ -179,35 +179,41 @@ export default class WalletsAdd extends Component {
<BlueSpacing20 />
<Text style={{ color: '#0c2550', fontWeight: '500' }}>{loc.settings.advanced_options}</Text>
<BlueListItem
containerStyle={{ paddingHorizontal: 0 }}
bottomDivider={false}
onPress={() => {
this.onSelect(0, HDSegwitBech32Wallet.type);
}}
title={HDSegwitBech32Wallet.typeReadable}
{...(this.state.selectedIndex === 0
? {
rightIcon: <Icon name="check" type="font-awesome" color="#0c2550" />,
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />,
}
: { hideChevron: true })}
/>
<BlueListItem
containerStyle={{ paddingHorizontal: 0 }}
bottomDivider={false}
onPress={() => {
this.onSelect(1, SegwitP2SHWallet.type);
}}
title={SegwitP2SHWallet.typeReadable}
{...(this.state.selectedIndex === 1
? {
rightIcon: <Icon name="check" type="font-awesome" color="#0c2550" />,
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />,
}
: { hideChevron: true })}
/>
<BlueListItem
containerStyle={{ paddingHorizontal: 0 }}
bottomDivider={false}
onPress={() => {
this.onSelect(2, HDSegwitP2SHWallet.typeReadable.type);
}}
title={HDSegwitP2SHWallet.typeReadable}
{...(this.state.selectedIndex === 2
? {
rightIcon: <Icon name="check" type="font-awesome" color="#0c2550" />,
rightIcon: <Icon name="check" type="octaicon" color="#0070FF" />,
}
: { hideChevron: true })}
/>

View file

@ -1,18 +1,33 @@
/* global alert */
import React, { Component } from 'react';
import { View, TouchableOpacity, Text, StyleSheet, InteractionManager, RefreshControl, SectionList, Alert, Platform } from 'react-native';
import {
StatusBar,
View,
TouchableOpacity,
Text,
StyleSheet,
InteractionManager,
Clipboard,
RefreshControl,
SectionList,
Alert,
Platform,
} from 'react-native';
import { BlueScanButton, WalletsCarousel, BlueHeaderDefaultMain, BlueTransactionListItem } from '../../BlueComponents';
import { Icon } from 'react-native-elements';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import PropTypes from 'prop-types';
import { PlaceholderWallet } from '../../class';
import { AppStorage, PlaceholderWallet } from '../../class';
import WalletImport from '../../class/walletImport';
let EV = require('../../events');
let A = require('../../analytics');
/** @type {AppStorage} */
let BlueApp = require('../../BlueApp');
let loc = require('../../loc');
let BlueElectrum = require('../../BlueElectrum');
import ActionSheet from '../ActionSheet';
import ImagePicker from 'react-native-image-picker';
const EV = require('../../events');
const A = require('../../analytics');
let BlueApp: AppStorage = require('../../BlueApp');
const loc = require('../../loc');
const BlueElectrum = require('../../BlueElectrum');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const WalletsListSections = { CAROUSEL: 'CAROUSEL', LOCALTRADER: 'LOCALTRADER', TRANSACTIONS: 'TRANSACTIONS' };
@ -25,7 +40,6 @@ export default class WalletsList extends Component {
isLoading: true,
isFlatListRefreshControlHidden: true,
wallets: BlueApp.getWallets().concat(false),
lastSnappedTo: 0,
timeElpased: 0,
dataSource: [],
};
@ -37,8 +51,11 @@ export default class WalletsList extends Component {
}
componentDidMount() {
console.log('wallets/list componentDidMount');
// the idea is that upon wallet launch we will refresh
// all balances and all transactions here:
this.redrawScreen();
InteractionManager.runAfterInteractions(async () => {
try {
await BlueElectrum.waitTillConnected();
@ -54,12 +71,13 @@ export default class WalletsList extends Component {
console.log(error);
}
});
this.interval = setInterval(() => {
this.setState(prev => ({ timeElapsed: prev.timeElapsed + 1 }));
}, 60000);
this.redrawScreen();
this._unsubscribe = this.props.navigation.addListener('focus', this.redrawScreen);
this._unsubscribe = this.props.navigation.addListener('focus', this.onNavigationEventFocus);
}
componentWillUnmount() {
@ -71,12 +89,7 @@ export default class WalletsList extends Component {
* Forcefully fetches TXs and balance for lastSnappedTo (i.e. current) wallet.
* Triggered manually by user on pull-to-refresh.
*/
refreshTransactions() {
if (!(this.lastSnappedTo < BlueApp.getWallets().length) && this.lastSnappedTo !== undefined) {
// last card, nop
console.log('last card, nop');
return;
}
refreshTransactions = () => {
this.setState(
{
isFlatListRefreshControlHidden: false,
@ -88,11 +101,11 @@ export default class WalletsList extends Component {
await BlueElectrum.ping();
await BlueElectrum.waitTillConnected();
let balanceStart = +new Date();
await BlueApp.fetchWalletBalances(this.lastSnappedTo || 0);
await BlueApp.fetchWalletBalances(this.walletsCarousel.current.currentIndex || 0);
let balanceEnd = +new Date();
console.log('fetch balance took', (balanceEnd - balanceStart) / 1000, 'sec');
let start = +new Date();
await BlueApp.fetchWalletTransactions(this.lastSnappedTo || 0);
await BlueApp.fetchWalletTransactions(this.walletsCarousel.current.currentIndex || 0);
let end = +new Date();
console.log('fetch tx took', (end - start) / 1000, 'sec');
} catch (err) {
@ -105,10 +118,16 @@ export default class WalletsList extends Component {
});
},
);
}
};
redrawScreen = (scrollToEnd = false) => {
console.log('wallets/list redrawScreen()');
// here, when we receive REMOTE_TRANSACTIONS_COUNT_CHANGED we fetch TXs and balance for current wallet.
// placing event subscription here so it gets exclusively re-subscribed more often. otherwise we would
// have to unsubscribe on unmount and resubscribe again on mount.
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED, this.refreshTransactions.bind(this), true);
if (BlueApp.getBalance() !== 0) {
A(A.ENUM.GOT_NONZERO_BALANCE);
} else {
@ -187,9 +206,6 @@ export default class WalletsList extends Component {
onSnapToItem = index => {
console.log('onSnapToItem', index);
this.lastSnappedTo = index;
this.setState({ lastSnappedTo: index });
if (index < BlueApp.getWallets().length) {
// not the last
}
@ -296,7 +312,7 @@ export default class WalletsList extends Component {
<View style={styles.headerStyle}>
<TouchableOpacity
testID="SettingsButton"
style={{ marginHorizontal: 16 }}
style={{ height: 48, paddingRight: 16, paddingLeft: 32, paddingVertical: 10 }}
onPress={() => this.props.navigation.navigate('Settings')}
>
<Icon size={22} name="kebab-horizontal" type="octicon" color={BlueApp.settings.foregroundColor} />
@ -434,7 +450,7 @@ export default class WalletsList extends Component {
overflow: 'hidden',
}}
>
<BlueScanButton onPress={this.onScanButtonPressed} />
<BlueScanButton onPress={this.onScanButtonPressed} onLongPress={this.sendButtonLongPress} />
</View>
);
} else {
@ -461,15 +477,99 @@ export default class WalletsList extends Component {
});
};
onNavigationEventFocus = () => {
StatusBar.setBarStyle('dark-content');
this.redrawScreen();
};
choosePhoto = () => {
ImagePicker.launchImageLibrary(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.path.toString();
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
this.onBarScanned(result);
} else {
alert('The selected image does not contain a QR Code.');
}
});
}
},
);
};
copyFromClipbard = async () => {
this.onBarScanned(await Clipboard.getString());
};
sendButtonLongPress = async () => {
const isClipboardEmpty = (await Clipboard.getString()).replace(' ', '').length === 0;
if (Platform.OS === 'ios') {
let options = [loc.send.details.cancel, 'Choose Photo', 'Scan QR Code'];
if (!isClipboardEmpty) {
options.push('Copy from Clipboard');
}
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, buttonIndex => {
if (buttonIndex === 1) {
this.choosePhoto();
} else if (buttonIndex === 2) {
this.props.navigation.navigate('ScanQRCode', {
launchedBy: this.props.route.name,
onBarScanned: this.onBarCodeRead,
showFileImportButton: false,
});
} else if (buttonIndex === 3) {
this.copyFromClipbard();
}
});
} else if (Platform.OS === 'android') {
let buttons = [
{
text: loc.send.details.cancel,
onPress: () => {},
style: 'cancel',
},
{
text: 'Choose Photo',
onPress: this.choosePhoto,
},
{
text: 'Scan QR Code',
onPress: () =>
this.props.navigation.navigate('ScanQRCode', {
launchedBy: this.props.route.name,
onBarScanned: this.onBarCodeRead,
showFileImportButton: false,
}),
},
];
if (!isClipboardEmpty) {
buttons.push({
text: 'Copy From Clipboard',
onPress: this.copyFromClipbard,
});
}
ActionSheet.showActionSheetWithOptions({
title: '',
message: '',
buttons,
});
}
};
render() {
return (
<View style={{ flex: 1 }}>
<View style={styles.walletsListWrapper}>
{this.renderNavigationHeader()}
<SectionList
refreshControl={
<RefreshControl onRefresh={() => this.refreshTransactions()} refreshing={!this.state.isFlatListRefreshControlHidden} />
}
refreshControl={<RefreshControl onRefresh={this.refreshTransactions} refreshing={!this.state.isFlatListRefreshControlHidden} />}
renderItem={this.renderSectionItem}
keyExtractor={this.sectionListKeyExtractor}
renderSectionHeader={this.renderSectionHeader}

View file

@ -12,12 +12,17 @@ const BlueApp = require('../../BlueApp');
const loc = require('../../loc');
const SelectWallet = () => {
const { chainType, onWalletSelect } = useRoute().params;
const { chainType, onWalletSelect, availableWallets } = useRoute().params;
const [isLoading, setIsLoading] = useState(true);
const data = chainType
let data = chainType
? BlueApp.getWallets().filter(item => item.chain === chainType && item.allowSend())
: BlueApp.getWallets().filter(item => item.allowSend()) || [];
if (availableWallets && availableWallets.length > 0) {
// availableWallets if provided, overrides chainType argument and `allowSend()` check
data = availableWallets;
}
useEffect(() => {
setIsLoading(false);
}, []);

View file

@ -82,7 +82,7 @@ export default class WalletTransactions extends Component {
super(props);
// here, when we receive REMOTE_TRANSACTIONS_COUNT_CHANGED we fetch TXs and balance for current wallet
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED, this.refreshTransactionsFunction.bind(this));
EV(EV.enum.REMOTE_TRANSACTIONS_COUNT_CHANGED, this.refreshTransactionsFunction.bind(this), true);
const wallet = props.route.params.wallet;
this.props.navigation.setParams({ wallet: wallet, isLoading: true });
this.state = {
@ -108,6 +108,7 @@ export default class WalletTransactions extends Component {
this._unsubscribeFocus = this.props.navigation.addListener('focus', () => {
StatusBar.setBarStyle('light-content');
if (Platform.OS === 'android') StatusBar.setBackgroundColor(WalletGradient.headerColorFor(this.props.route.params.wallet.type));
this.redrawScreen();
this.props.navigation.setParams({ isLoading: false });
});
@ -471,6 +472,7 @@ export default class WalletTransactions extends Component {
onWillBlur() {
StatusBar.setBarStyle('dark-content');
if (Platform.OS === 'android') StatusBar.setBackgroundColor('#ffffff');
}
componentWillUnmount() {
@ -546,52 +548,57 @@ export default class WalletTransactions extends Component {
this.onBarCodeRead({ data: await Clipboard.getString() });
};
sendButtonLongPress = () => {
sendButtonLongPress = async () => {
const isClipboardEmpty = (await Clipboard.getString()).replace(' ', '').length === 0;
if (Platform.OS === 'ios') {
ActionSheet.showActionSheetWithOptions(
{ options: [loc.send.details.cancel, 'Choose Photo', 'Scan QR Code', 'Copy from Clipboard'], cancelButtonIndex: 0 },
buttonIndex => {
if (buttonIndex === 1) {
this.choosePhoto();
} else if (buttonIndex === 2) {
let options = [loc.send.details.cancel, 'Choose Photo', 'Scan QR Code'];
if (!isClipboardEmpty) {
options.push('Copy from Clipboard');
}
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, buttonIndex => {
if (buttonIndex === 1) {
this.choosePhoto();
} else if (buttonIndex === 2) {
this.props.navigation.navigate('ScanQRCode', {
launchedBy: this.props.route.name,
onBarScanned: this.onBarCodeRead,
showFileImportButton: false,
});
} else if (buttonIndex === 3) {
this.copyFromClipbard();
}
});
} else if (Platform.OS === 'android') {
let buttons = [
{
text: loc.send.details.cancel,
onPress: () => {},
style: 'cancel',
},
{
text: 'Choose Photo',
onPress: this.choosePhoto,
},
{
text: 'Scan QR Code',
onPress: () =>
this.props.navigation.navigate('ScanQRCode', {
launchedBy: this.props.route.name,
onBarScanned: this.onBarCodeRead,
showFileImportButton: false,
});
} else if (buttonIndex === 3) {
this.copyFromClipbard();
}
}),
},
);
} else if (Platform.OS === 'android') {
];
if (!isClipboardEmpty) {
buttons.push({
text: 'Copy From Clipboard',
onPress: this.copyFromClipbard,
});
}
ActionSheet.showActionSheetWithOptions({
title: '',
message: '',
buttons: [
{
text: loc.send.details.cancel,
onPress: () => {},
style: 'cancel',
},
{
text: 'Choose Photo',
onPress: this.choosePhoto,
},
{
text: 'Scan QR Code',
onPress: () =>
this.props.navigation.navigate('ScanQRCode', {
launchedBy: this.props.route.name,
onBarScanned: this.onBarCodeRead,
showFileImportButton: false,
}),
},
{
text: 'Copy From Clipboard',
onPress: this.copyFromClipbard,
},
],
buttons,
});
}
};

View file

@ -7,6 +7,8 @@ const branch = require('child_process')
.toString()
.trim();
if (branch === 'master') process.exit();
const req = https.request(
{
hostname: 'api.github.com',

View file

@ -1,10 +1,21 @@
/* global it, describe, expect, element, by, waitFor, device */
/* global it, describe, expect, element, by, waitFor, device, jasmine */
const bitcoin = require('bitcoinjs-lib');
const assert = require('assert');
const createHash = require('create-hash');
jasmine.getEnv().addReporter({
specStarted: result => (jasmine.currentTest = result),
specDone: result => (jasmine.currentTest = result),
});
describe('BlueWallet UI Tests', () => {
it('selftest passes', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', jasmine.currentTest.fullName, 'as it previously passed on Travis');
}
await waitFor(element(by.id('WalletsList')))
.toBeVisible()
.withTimeout(300 * 1000);
@ -17,9 +28,15 @@ describe('BlueWallet UI Tests', () => {
await waitFor(element(by.id('SelfTestOk')))
.toBeVisible()
.withTimeout(300 * 1000);
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it('can create wallet, reload app and it persists', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', jasmine.currentTest.fullName, 'as it previously passed on Travis');
}
await yo('WalletsList');
await helperCreateWallet();
@ -27,9 +44,15 @@ describe('BlueWallet UI Tests', () => {
await device.launchApp({ newInstance: true });
await yo('WalletsList');
await expect(element(by.id('cr34t3d'))).toBeVisible();
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it('can encrypt storage, with plausible deniability', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', jasmine.currentTest.fullName, 'as it previously passed on Travis');
}
await yo('WalletsList');
// lets create a wallet
@ -162,9 +185,15 @@ describe('BlueWallet UI Tests', () => {
// previously created wallet in FAKE storage should be visible
await expect(element(by.id('fake_wallet'))).toBeVisible();
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it('can encrypt storage, and decrypt storage works', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', jasmine.currentTest.fullName, 'as it previously passed on Travis');
}
await yo('WalletsList');
await helperCreateWallet();
await element(by.id('SettingsButton')).tap();
@ -232,9 +261,15 @@ describe('BlueWallet UI Tests', () => {
// relaunch app
await device.launchApp({ newInstance: true });
await yo('cr34t3d'); // success
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it.skip('can encrypt storage, and decrypt storage, but this time the fake one', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', jasmine.currentTest.fullName, 'as it previously passed on Travis');
}
// this test mostly repeats previous one, except in the end it logins with FAKE password to unlock FAKE
// storage bucket, and then decrypts it. effectively, everything from MAIN storage bucket is lost
if (process.env.TRAVIS) return; // skipping on CI to not take time (plus it randomly fails)
@ -305,9 +340,15 @@ describe('BlueWallet UI Tests', () => {
// relaunch app
await device.launchApp({ newInstance: true });
await yo('fake_wallet'); // success, we are observing wallet in FAKE storage. wallet from main storage is lost
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it('can import BIP84 mnemonic, fetch balance & transactions, then create a transaction', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', jasmine.currentTest.fullName, 'as it previously passed on Travis');
}
if (!process.env.HD_MNEMONIC_BIP84) {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
@ -361,9 +402,15 @@ describe('BlueWallet UI Tests', () => {
assert.strictEqual(transaction.outs.length, 2);
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[0].script), 'bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl'); // to address
assert.strictEqual(transaction.outs[0].value, 10000);
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it('can import zpub as watch-only and create PSBT', async () => {
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile))
return console.warn('skipping', jasmine.currentTest.fullName, 'as it previously passed on Travis');
}
await helperImportWallet(
'zpub6r7jhKKm7BAVx3b3nSnuadY1WnshZYkhK8gKFoRLwK9rF3Mzv28BrGcCGA3ugGtawi1WLb2vyjQAX9ZTDGU5gNk2bLdTc3iEXr6tzR1ipNP',
'Imported Watch-only',
@ -381,6 +428,7 @@ describe('BlueWallet UI Tests', () => {
} catch (_) {}
await yo('TextHelperForPSBT');
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
});
@ -438,3 +486,10 @@ async function helperImportWallet(importText, expectedWalletLabel, expectedBalan
// label might change in the future
expect(element(by.id('WalletBalance'))).toHaveText(expectedBalance);
}
function hashIt(s) {
return createHash('sha256')
.update(s)
.digest()
.toString('hex');
}

View file

@ -210,6 +210,7 @@ describe('LightningCustodianWallet', () => {
const api = new Frisbee({
baseURI: 'https://api.strike.acinq.co',
headers: {},
});
api.auth(process.env.STRIKE + ':');

View file

@ -127,6 +127,18 @@ describe('unit - DeepLinkSchemaMatch', function() {
},
],
},
{
argument: {
url: 'https://azte.co/?c1=3062&c2=2586&c3=5053&c4=5261',
},
expected: [
'AztecoRedeemRoot',
{
screen: 'AztecoRedeem',
params: { c1: '3062', c2: '2586', c3: '5053', c4: '5261', uri: 'https://azte.co/?c1=3062&c2=2586&c3=5053&c4=5261' },
},
],
},
];
const asyncNavigationRouteFor = async function(event) {