Merge branch 'master' into privacywidget

This commit is contained in:
marcosrdz 2020-12-24 14:33:26 -05:00
commit 56b4afd577
22 changed files with 310 additions and 240 deletions

25
App.js
View file

@ -153,7 +153,6 @@ const App = () => {
const notifications2process = await Notifications.getStoredNotifications();
for (const payload of notifications2process) {
const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction === true);
if (!wasTapped) continue;
console.log('processing push notification:', payload);
let wallet;
@ -171,16 +170,19 @@ const App = () => {
if (wallet) {
const walletID = wallet.getID();
fetchAndSaveWalletTransactions(walletID);
NavigationService.dispatch(
CommonActions.navigate({
name: 'WalletTransactions',
key: `WalletTransactions-${wallet.getID()}`,
params: {
walletID,
walletType: wallet.type,
},
}),
);
if (wasTapped) {
NavigationService.dispatch(
CommonActions.navigate({
name: 'WalletTransactions',
key: `WalletTransactions-${wallet.getID()}`,
params: {
walletID,
walletType: wallet.type,
},
}),
);
}
// no delay (1ms) as we dont need to wait for transaction propagation. 500ms is a delay to wait for the navigation
await Notifications.clearStoredNotifications();
return true;
@ -192,7 +194,6 @@ const App = () => {
// TODO: if we are here - we did not act upon any push, so we need to iterate over _not tapped_ pushes
// and refetch appropriate wallet and redraw screen
await Notifications.clearStoredNotifications();
return false;
};

View file

@ -1,5 +1,5 @@
import PushNotificationIOS from '@react-native-community/push-notification-ios';
import { Alert, Platform } from 'react-native';
import { Alert } from 'react-native';
import Frisbee from 'frisbee';
import { getApplicationName, getVersion, getSystemName, getSystemVersion } from 'react-native-device-info';
import AsyncStorage from '@react-native-async-storage/async-storage';
@ -54,14 +54,7 @@ function Notifications(props) {
//
// ...we save notification in internal notifications queue thats gona be processed later (on unsuspend with decrypted storage)
if (Platform.OS === 'ios' && notification.foreground === true && notification.userInteraction === false) {
// iOS hack
// @see https://github.com/zo0r/react-native-push-notification/issues/1585
notification.userInteraction = true;
// also, on iOS app is not suspending/unsuspending when user taps a notification bubble,so we simulate it
// since its where we actually handle notifications:
setTimeout(() => props.onProcessNotifications(), 500);
}
setTimeout(() => props.onProcessNotifications(), 500);
let notifications = [];
try {

View file

@ -181,7 +181,6 @@ class DeeplinkSchemaMatch {
]);
} else {
const urlObject = url.parse(event.url, true); // eslint-disable-line node/no-deprecated-api
console.log('parsed', event.url, 'into', urlObject);
(async () => {
if (urlObject.protocol === 'bluewallet:' || urlObject.protocol === 'lapp:' || urlObject.protocol === 'blue:') {
switch (urlObject.host) {
@ -244,12 +243,56 @@ class DeeplinkSchemaMatch {
]);
break;
}
case 'setelectrumserver':
completionHandler([
'ElectrumSettings',
{
server: DeeplinkSchemaMatch.getServerFromSetElectrumServerAction(event.url),
},
]);
break;
case 'setlndhuburl':
completionHandler([
'LightningSettings',
{
url: DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction(event.url),
},
]);
break;
}
}
})();
}
}
/**
* Extracts server from a deeplink like `bluewallet:setelectrumserver?server=electrum1.bluewallet.io%3A443%3As`
* returns FALSE if none found
*
* @param url {string}
* @return {string|boolean}
*/
static getServerFromSetElectrumServerAction(url) {
if (!url.startsWith('bluewallet:setelectrumserver') && !url.startsWith('setelectrumserver')) return false;
const splt = url.split('server=');
if (splt[1]) return decodeURIComponent(splt[1]);
return false;
}
/**
* Extracts url from a deeplink like `bluewallet:setlndhuburl?url=https%3A%2F%2Flndhub.herokuapp.com`
* returns FALSE if none found
*
* @param url {string}
* @return {string|boolean}
*/
static getUrlFromSetLndhubUrlAction(url) {
if (!url.startsWith('bluewallet:setlndhuburl') && !url.startsWith('setlndhuburl')) return false;
const splt = url.split('url=');
if (splt[1]) return decodeURIComponent(splt[1]);
return false;
}
static isTXNFile(filePath) {
return (
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&

View file

@ -276,8 +276,8 @@ export class LegacyWallet extends AbstractWallet {
if (!changeAddress) throw new Error('No change address provided');
let algo = coinSelectAccumulative;
if (targets.length === 1 && targets[0] && !targets[0].value) {
// we want to send MAX
// if targets has output without a value, we want send MAX to it
if (targets.some(i => !('value' in i))) {
algo = coinSelectSplit;
}
@ -285,7 +285,7 @@ export class LegacyWallet extends AbstractWallet {
// .inputs and .outputs will be undefined if no solution was found
if (!inputs || !outputs) {
throw new Error('Not enough balance. Try sending smaller amount');
throw new Error('Not enough balance. Try sending smaller amount or decrease the fee.');
}
return { inputs, outputs, fee };

View file

@ -3,8 +3,6 @@ import bip39 from 'bip39';
import b58 from 'bs58check';
import { decodeUR } from 'bc-ur';
const BlueElectrum = require('../../blue_modules/BlueElectrum');
const coinSelectAccumulative = require('coinselect/accumulative');
const coinSelectSplit = require('coinselect/split');
const HDNode = require('bip32');
const bitcoin = require('bitcoinjs-lib');
const createHash = require('create-hash');
@ -809,22 +807,9 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet {
if (targets.length === 0) throw new Error('No destination provided');
if (this.howManySignaturesCanWeMake() === 0) skipSigning = true;
if (!changeAddress) throw new Error('No change address provided');
const { inputs, outputs, fee } = this.coinselect(utxos, targets, feeRate, changeAddress);
sequence = sequence || AbstractHDElectrumWallet.defaultRBFSequence;
let algo = coinSelectAccumulative;
if (targets.length === 1 && targets[0] && !targets[0].value) {
// we want to send MAX
algo = coinSelectSplit;
}
const { inputs, outputs, fee } = algo(utxos, targets, feeRate);
// .inputs and .outputs will be undefined if no solution was found
if (!inputs || !outputs) {
throw new Error('Not enough balance. Try sending smaller amount');
}
let psbt = new bitcoin.Psbt();
let c = 0;

View file

@ -91,48 +91,42 @@ static void InitializeFlipper(UIApplication *application) {
[RNQuickActionManager onQuickActionPress:shortcutItem completionHandler:completionHandler];
}
//Called when a notification is delivered to a foreground app.
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
NSDictionary *userInfo = notification.request.content.userInfo;
//Foreground
NSLog(@"APP_PUSH from foreground %@", userInfo);
[RNCPushNotificationIOS didReceiveRemoteNotification:userInfo
fetchCompletionHandler:^void (UIBackgroundFetchResult result){}];
completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
}
// Required to register for notifications
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[RNCPushNotificationIOS didRegisterUserNotificationSettings:notificationSettings];
}
// Required for the register event.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
[RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
// Required for the notification event. You must call the completion handler after handling the remote notification.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
[RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
// Required for the registrationError event.
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
[RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
}
// IOS 10+ Required for localNotification event
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
@ -141,19 +135,11 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
[RNCPushNotificationIOS didReceiveNotificationResponse:response];
completionHandler();
}
// IOS 4-10 Required for the localNotification event.
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
[RNCPushNotificationIOS didReceiveLocalNotification:notification];
}
@end

View file

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>io.bluewallet.bluewallet.fetchTxsForWallet</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
@ -186,6 +190,8 @@
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>

View file

@ -355,7 +355,7 @@ PODS:
- React-Core
- RNCMaskedView (0.1.10):
- React
- RNCPushNotificationIOS (1.7.1):
- RNCPushNotificationIOS (1.8.0):
- React-Core
- RNDefaultPreference (1.4.3):
- React
@ -727,7 +727,7 @@ SPEC CHECKSUMS:
RNCAsyncStorage: bc2f81cc1df90c267ce9ed30bb2dbc93b945a8ee
RNCClipboard: 8f9f12fabf3c06e976f19f87a62c89e28dfedfca
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
RNCPushNotificationIOS: eaf01f848a0b872b194d31bcad94bb864299e01e
RNCPushNotificationIOS: 5b1cf9ad2aaa107ecb92d5d2d7005ba521b2b97a
RNDefaultPreference: 21816c0a6f61a2829ccc0cef034392e9b509ee5f
RNDeviceInfo: 57bb2806fb7bd982a1434e9f0b4e6a6ab1f6702e
RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df

View file

@ -249,6 +249,8 @@
"electrum_port": "TCP port, usually {example}",
"electrum_port_ssl": "SSL port, usually {example}",
"electrum_saved": "Your changes have been saved successfully. Restart may be required for changes to take effect.",
"set_electrum_server_as_default": "Set {server} as default electrum server?",
"set_lndhub_as_default": "Set {url} as default LNDHub server?",
"electrum_settings": "Electrum Settings",
"electrum_settings_explain": "Set to blank to use default",
"electrum_status": "Status",

View file

@ -232,7 +232,8 @@
"about_review": "Laissez-nous votre avis",
"about_selftest": "Effectuer un auto-test",
"about_sm_github": "GitHub",
"about_sm_telegram": "Discussion Telegram",
"about_sm_discord": "Serveur discord",
"about_sm_telegram": "Chaîne Telegram",
"about_sm_twitter": "Nous suivre sur Twitter",
"advanced_options": "Options avancées",
"currency": "Devise",
@ -291,7 +292,7 @@
"privacy_clipboard_explanation": "Fourni un raccourci si une adresse ou une facture est trouvée dans le presse-papier.",
"push_notifications": "Notifications push",
"retype_password": "Re-saisir votre mot de passe",
"save": "save",
"save": "Enregistrer",
"saved": "Enregistré",
"success_transaction_broadcasted" : "Succès! Votre transaction a été difusée!"
},
@ -365,7 +366,7 @@
"details_marketplace": "Marketplace",
"details_master_fingerprint": "Empreinte maitresse",
"details_no_cancel": "Non, annuler",
"details_save": "Sauvegarder",
"details_save": "Enregistrer",
"details_show_xpub": "Afficher XPUB du portefeuille",
"details_title": "Portefeuille",
"details_type": "Type",
@ -402,6 +403,7 @@
"list_tryagain": "Réessayer",
"looks_like_bip38": "Ceci ressemble a une clé privée protégée par un mot de passe (BIP38)",
"reorder_title": "Trier vos portefeuilles",
"please_continue_scanning": "Merci de continuer à scaner",
"select_no_bitcoin": "Il n'y a aucun portefeuille Bitcoin disponible pour le moment.",
"select_no_bitcoin_exp": "Un portefeuille Bitcoin est nécessaire pour approvisionner les portefeuilles Lightning. Veuillez en créer ou en importer un.",
"select_wallet": "Choix du portefeuille",

View file

@ -232,7 +232,8 @@
"about_review": "Laat een review achter",
"about_selftest": "Voer een zelftest uit",
"about_sm_github": "GitHub",
"about_sm_telegram": "Telegram chat",
"about_sm_discord": "Discord server",
"about_sm_telegram": "Telegram kanaal",
"about_sm_twitter": "Volg ons op Twitter",
"advanced_options": "Geavanceerde opties",
"currency": "Valuta",
@ -402,6 +403,7 @@
"list_tryagain": "Probeer opnieuw",
"looks_like_bip38": "Dit lijkt op een met een wachtwoord beveiligde private key (BIP38)",
"reorder_title": "Wallets opnieuw ordenen",
"please_continue_scanning": "Ga door met scannen",
"select_no_bitcoin": "Er is momenteel geen Bitcoin-wallet beschikbaar",
"select_no_bitcoin_exp": "Een Bitcoin-wallet is vereist om Lightning-wallets opnieuw te vullen. Maak of importeer er een.",
"select_wallet": "Selecteer wallet",
@ -443,6 +445,7 @@
"needs": "Het is nodig",
"what_is_vault_description_number_of_vault_keys": "{m} Vault Keys",
"what_is_vault_description_to_spend": "te besteden en een 3e die u \nals back-up kunt gebruiken.",
"what_is_vault_description_to_spend_other": "om uit te geven.",
"quorum": "{m} van {n} quorum",
"quorum_header": "Quorum",
"of": "van",

6
package-lock.json generated
View file

@ -6392,9 +6392,9 @@
"integrity": "sha512-rk4sWFsmtOw8oyx8SD3KSvawwaK7gRBSEIy2TAwURyGt+3TizssXP1r8nx3zY+R7v2vYYHXZ+k2/GULAT/bcaQ=="
},
"@react-native-community/push-notification-ios": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@react-native-community/push-notification-ios/-/push-notification-ios-1.7.1.tgz",
"integrity": "sha512-cx535+qyGbGTkx5GEHcF+Yd3fT9k1L114VeqQ5hkDYwU8unkKAiItjQz7Utvj/mcffwhqvPFU2FI3eKM3R1VYw==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@react-native-community/push-notification-ios/-/push-notification-ios-1.8.0.tgz",
"integrity": "sha512-vxvkeampafjmtDoQBN8Q4azP21l6cMX93+OQZemyIWZmG++OjCVDQVitobf/kWLm5zyGwdylejbpMGo75qo7rA==",
"requires": {
"invariant": "^2.2.4"
}

View file

@ -71,7 +71,7 @@
"@react-native-community/clipboard": "1.5.0",
"@react-native-community/geolocation": "2.0.2",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/push-notification-ios": "1.7.1",
"@react-native-community/push-notification-ios": "1.8.0",
"@react-native-community/slider": "3.0.3",
"@react-navigation/drawer": "5.11.2",
"@react-navigation/native": "5.8.9",

View file

@ -557,6 +557,11 @@ export default class SendDetails extends Component {
}
}
// if targets is empty, insert dust
if (targets.length === 0) {
targets.push({ address: '36JxaUrpDzkEerkTf1FzwHNE1Hb7cCjgJV', value: 546 });
}
// replace wrong addresses with dump
targets = targets.map(t => {
try {
@ -570,21 +575,15 @@ export default class SendDetails extends Component {
let flag = false;
while (true) {
try {
const { fee } = wallet.coinselect(
utxo,
targets,
opt.fee,
changeAddress,
this.state.isTransactionReplaceable ? HDSegwitBech32Wallet.defaultRBFSequence : HDSegwitBech32Wallet.finalRBFSequence,
);
const { fee } = wallet.coinselect(utxo, targets, opt.fee, changeAddress);
feePrecalc[opt.key] = fee;
break;
} catch (e) {
if (e.message.includes('Not enough') && !flag) {
flag = true;
// if the outputs are too big, replace them with dust
targets = targets.map(t => ({ ...t, value: 546 }));
// if we don't have enough funds, construct maximum possible transaction
targets = targets.map((t, index) => (index > 0 ? { ...t, value: 546 } : { address: t.address }));
continue;
}
@ -1291,13 +1290,15 @@ export default class SendDetails extends Component {
item.address = address || text;
item.amount = amount || item.amount;
transactions[index] = item;
this.setState({
addresses: transactions,
memo: memo || this.state.memo,
isLoading: false,
payjoinUrl,
});
this.reCalcTx();
this.setState(
{
addresses: transactions,
memo: memo || this.state.memo,
isLoading: false,
payjoinUrl,
},
this.reCalcTx,
);
}}
onBarScanned={this.processAddressData}
address={item.address}
@ -1326,11 +1327,14 @@ export default class SendDetails extends Component {
recipient.amount = BitcoinUnit.MAX;
recipient.amountSats = BitcoinUnit.MAX;
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({
addresses: [recipient],
units: [BitcoinUnit.BTC],
isAdvancedTransactionOptionsVisible: false,
});
this.setState(
{
addresses: [recipient],
units: [BitcoinUnit.BTC],
isAdvancedTransactionOptionsVisible: false,
},
this.reCalcTx,
);
},
style: 'default',
},

View file

@ -1,5 +1,5 @@
import React, { useState, useContext } from 'react';
import { StyleSheet, View, KeyboardAvoidingView, Platform, TextInput } from 'react-native';
import { StyleSheet, View, KeyboardAvoidingView, Platform, TextInput, Keyboard } from 'react-native';
import loc from '../../loc';
import {
SafeBlueArea,
@ -41,6 +41,7 @@ const IsItMyAddress = () => {
const handleUpdateAddress = nextValue => setAddress(nextValue.trim());
const checkAddress = () => {
Keyboard.dismiss();
const cleanAddress = address.replace('bitcoin:', '').replace('BITCOIN:', '').replace('bitcoin=', '').split('?')[0];
const _result = [];
for (const w of wallets) {

View file

@ -13,7 +13,6 @@ import {
StyleSheet,
Alert,
} from 'react-native';
import { launchCamera } from 'react-native-image-picker';
import Clipboard from '@react-native-community/clipboard';
import {
SecondButton,
@ -31,7 +30,6 @@ import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import RNFS from 'react-native-fs';
import DocumentPicker from 'react-native-document-picker';
import loc from '../../loc';
import ScanQRCode from './ScanQRCode';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import Notifications from '../../blue_modules/notifications';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
@ -39,8 +37,8 @@ import isCatalyst from 'react-native-is-catalyst';
const BlueElectrum = require('../../blue_modules/BlueElectrum');
/** @type {AppStorage} */
const bitcoin = require('bitcoinjs-lib');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const isDesktop = getSystemName() === 'Mac OS X';
const fs = require('../../blue_modules/fs');
const PsbtWithHardwareWallet = () => {
const { txMetadata, fetchAndSaveWalletTransactions } = useContext(BlueStorageContext);
@ -244,27 +242,7 @@ const PsbtWithHardwareWallet = () => {
const openScanner = () => {
if (isDesktop) {
launchCamera(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.uri;
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
} else if (response.error) {
ScanQRCode.presentCameraNotAuthorizedAlert(response.error);
}
},
);
fs.showActionSheet().then(data => onBarScanned({ data }));
} else {
navigation.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',

View file

@ -1,6 +1,6 @@
/* global alert */
import React, { Component } from 'react';
import { View, TextInput, StyleSheet } from 'react-native';
import { Alert, View, TextInput, StyleSheet } from 'react-native';
import { AppStorage } from '../../class';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { ScrollView } from 'react-native-gesture-handler';
@ -19,14 +19,17 @@ import PropTypes from 'prop-types';
import loc from '../../loc';
import DefaultPreference from 'react-native-default-preference';
import RNWidgetCenter from 'react-native-widget-center';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
const BlueElectrum = require('../../blue_modules/BlueElectrum');
export default class ElectrumSettings extends Component {
constructor(props) {
super(props);
const server = props?.route?.params?.server;
this.state = {
isLoading: true,
config: {},
server,
};
}
@ -56,6 +59,24 @@ export default class ElectrumSettings extends Component {
config: await BlueElectrum.getConfig(),
inverval,
});
if (this.state.server) {
Alert.alert(
loc.formatString(loc.settings.set_electrum_server_as_default, { server: this.state.server }),
'',
[
{
text: loc._.ok,
onPress: () => {
this.onBarScanned(this.state.server);
},
style: 'default',
},
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
}
}
checkServer = async () => {
@ -115,6 +136,10 @@ export default class ElectrumSettings extends Component {
};
onBarScanned = value => {
if (DeeplinkSchemaMatch.getServerFromSetElectrumServerAction(value)) {
// in case user scans a QR with a deeplink like `bluewallet:setelectrumserver?server=electrum1.bluewallet.io%3A443%3As`
value = DeeplinkSchemaMatch.getServerFromSetElectrumServerAction(value);
}
var [host, port, type] = value.split(':');
this.setState({ host: host });
type === 's' ? this.setState({ sslPort: port }) : this.setState({ port: port });
@ -216,6 +241,9 @@ ElectrumSettings.propTypes = {
}),
route: PropTypes.shape({
name: PropTypes.string,
params: PropTypes.shape({
server: PropTypes.string,
}),
}),
};

View file

@ -1,6 +1,6 @@
/* global alert */
import React, { useState, useEffect, useCallback } from 'react';
import { View, TextInput, Linking, StyleSheet } from 'react-native';
import { View, TextInput, Linking, StyleSheet, Alert } from 'react-native';
import { Button } from 'react-native-elements';
import { useTheme, useNavigation, useRoute } from '@react-navigation/native';
import { AppStorage } from '../../class';
@ -18,6 +18,7 @@ import {
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import loc from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
const styles = StyleSheet.create({
root: {
@ -48,6 +49,7 @@ const styles = StyleSheet.create({
});
const LightningSettings = () => {
const params = useRoute().params;
const [isLoading, setIsLoading] = useState(true);
const [URI, setURI] = useState();
const { colors } = useTheme();
@ -59,9 +61,31 @@ const LightningSettings = () => {
.then(setURI)
.then(() => setIsLoading(false))
.catch(() => setIsLoading(false));
}, []);
if (params?.url) {
Alert.alert(
loc.formatString(loc.settings.set_lndhub_as_default, { url: params?.url }),
'',
[
{
text: loc._.ok,
onPress: () => {
setLndhubURI(params?.url);
},
style: 'default',
},
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
}
}, [params?.url]);
const setLndhubURI = value => {
if (DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction(value)) {
// in case user scans a QR with a deeplink like `bluewallet:setlndhuburl?url=https%3A%2F%2Flndhub.herokuapp.com`
value = DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction(value);
}
setURI(value.trim());
};

View file

@ -311,7 +311,6 @@ const WalletsAddMultisigStep2 = () => {
};
const onBarScanned = ret => {
setIsProvideMnemonicsModalVisible(false);
if (!isDesktop) navigation.dangerouslyGetParent().pop();
if (!ret.data) ret = { data: ret };
if (ret.data.toUpperCase().startsWith('UR')) {
@ -322,7 +321,7 @@ const WalletsAddMultisigStep2 = () => {
} else {
let cosigner = new MultisigCosigner(ret.data);
if (!cosigner.isValid()) return alert(loc.multisig.invalid_cosigner);
setIsProvideMnemonicsModalVisible(false);
if (cosigner.howManyCosignersWeHave() > 1) {
// lets look for the correct cosigner. thats probably gona be the one with specific corresponding path,
// for example m/48'/0'/0'/2' if user chose to setup native segwit in BW
@ -390,8 +389,9 @@ const WalletsAddMultisigStep2 = () => {
const scanOrOpenFile = () => {
if (isDesktop) {
fs.showActionSheet().then(data => onBarScanned({ data }));
fs.showActionSheet().then(onBarScanned);
} else {
setIsProvideMnemonicsModalVisible(false);
navigation.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {

View file

@ -1,4 +1,3 @@
/* global alert */
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import {
StatusBar,
@ -21,17 +20,14 @@ import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { PlaceholderWallet } from '../../class';
import WalletImport from '../../class/wallet-import';
import ActionSheet from '../ActionSheet';
import { launchImageLibrary, launchCamera } from 'react-native-image-picker';
import Clipboard from '@react-native-community/clipboard';
import loc from '../../loc';
import { FContainer, FButton } from '../../components/FloatButtons';
import { getSystemName, isTablet } from 'react-native-device-info';
import { presentCameraNotAuthorizedAlert } from '../../class/camera';
import { useFocusEffect, useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import isCatalyst from 'react-native-is-catalyst';
const A = require('../../blue_modules/analytics');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const fs = require('../../blue_modules/fs');
const WalletsListSections = { CAROUSEL: 'CAROUSEL', LOCALTRADER: 'LOCALTRADER', TRANSACTIONS: 'TRANSACTIONS' };
const isDesktop = getSystemName() === 'Mac OS X';
@ -362,52 +358,6 @@ const WalletsList = () => {
});
};
const choosePhoto = () => {
launchImageLibrary(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.uri;
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
}
},
);
};
const takePhoto = () => {
launchCamera(
{
title: null,
mediaType: 'photo',
takePhotoButtonTitle: null,
},
response => {
if (response.uri) {
const uri = Platform.OS === 'ios' ? response.uri.toString().replace('file://', '') : response.uri;
LocalQRCode.decode(uri, (error, result) => {
if (!error) {
onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
} else if (response.error) {
presentCameraNotAuthorizedAlert(response.error);
}
},
);
};
const copyFromClipboard = async () => {
onBarScanned(await Clipboard.getString());
};
@ -415,17 +365,17 @@ const WalletsList = () => {
const sendButtonLongPress = async () => {
const isClipboardEmpty = (await Clipboard.getString()).replace(' ', '').length === 0;
if (Platform.OS === 'ios') {
const options = [loc._.cancel, loc.wallets.list_long_choose, isDesktop ? loc.wallets.take_photo : loc.wallets.list_long_scan];
if (!isClipboardEmpty) {
options.push(loc.wallets.list_long_clipboard);
}
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, buttonIndex => {
if (buttonIndex === 1) {
choosePhoto();
} else if (buttonIndex === 2) {
if (isDesktop) {
takePhoto();
} else {
if (isDesktop) {
fs.showActionSheet().then(onBarScanned);
} else {
const options = [loc._.cancel, loc.wallets.list_long_choose, loc.wallets.list_long_scan];
if (!isClipboardEmpty) {
options.push(loc.wallets.list_long_clipboard);
}
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, buttonIndex => {
if (buttonIndex === 1) {
fs.showImagePickerAndReadImage().then(onBarScanned);
} else if (buttonIndex === 2) {
navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
@ -434,11 +384,11 @@ const WalletsList = () => {
showFileImportButton: false,
},
});
} else if (buttonIndex === 3) {
copyFromClipboard();
}
} else if (buttonIndex === 3) {
copyFromClipboard();
}
});
});
}
} else if (Platform.OS === 'android') {
const buttons = [
{
@ -448,7 +398,7 @@ const WalletsList = () => {
},
{
text: loc.wallets.list_long_choose,
onPress: choosePhoto,
onPress: () => fs.showActionSheet().then(onBarScanned),
},
{
text: loc.wallets.list_long_scan,

View file

@ -35,8 +35,11 @@ import isCatalyst from 'react-native-is-catalyst';
import BottomModal from '../../components/BottomModal';
import BuyBitcoin from './buyBitcoin';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { getSystemName } from 'react-native-device-info';
const fs = require('../../blue_modules/fs');
const BlueElectrum = require('../../blue_modules/BlueElectrum');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const isDesktop = getSystemName() === 'Mac OS X';
const buttonFontSize =
PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22
@ -509,42 +512,19 @@ const WalletTransactions = () => {
};
const sendButtonLongPress = async () => {
const isClipboardEmpty = (await Clipboard.getString()).replace(' ', '').length === 0;
if (Platform.OS === 'ios') {
const options = [loc._.cancel, loc.wallets.list_long_choose, loc.wallets.list_long_scan];
if (!isClipboardEmpty) {
options.push(loc.wallets.list_long_clipboard);
}
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, buttonIndex => {
if (buttonIndex === 1) {
choosePhoto();
} else if (buttonIndex === 2) {
navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
launchedBy: name,
onBarScanned: onBarCodeRead,
showFileImportButton: false,
},
});
} else if (buttonIndex === 3) {
copyFromClipboard();
if (isDesktop) {
fs.showActionSheet().then(onBarCodeRead);
} else {
const isClipboardEmpty = (await Clipboard.getString()).replace(' ', '').length === 0;
if (Platform.OS === 'ios') {
const options = [loc._.cancel, loc.wallets.list_long_choose, loc.wallets.list_long_scan];
if (!isClipboardEmpty) {
options.push(loc.wallets.list_long_clipboard);
}
});
} else if (Platform.OS === 'android') {
const buttons = [
{
text: loc._.cancel,
onPress: () => {},
style: 'cancel',
},
{
text: loc.wallets.list_long_choose,
onPress: choosePhoto,
},
{
text: loc.wallets.list_long_scan,
onPress: () =>
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, buttonIndex => {
if (buttonIndex === 1) {
choosePhoto();
} else if (buttonIndex === 2) {
navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
@ -552,20 +532,47 @@ const WalletTransactions = () => {
onBarScanned: onBarCodeRead,
showFileImportButton: false,
},
}),
},
];
if (!isClipboardEmpty) {
buttons.push({
text: loc.wallets.list_long_clipboard,
onPress: copyFromClipboard,
});
} else if (buttonIndex === 3) {
copyFromClipboard();
}
});
} else if (Platform.OS === 'android') {
const buttons = [
{
text: loc._.cancel,
onPress: () => {},
style: 'cancel',
},
{
text: loc.wallets.list_long_choose,
onPress: choosePhoto,
},
{
text: loc.wallets.list_long_scan,
onPress: () =>
navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
launchedBy: name,
onBarScanned: onBarCodeRead,
showFileImportButton: false,
},
}),
},
];
if (!isClipboardEmpty) {
buttons.push({
text: loc.wallets.list_long_clipboard,
onPress: copyFromClipboard,
});
}
ActionSheet.showActionSheetWithOptions({
title: '',
message: '',
buttons,
});
}
ActionSheet.showActionSheetWithOptions({
title: '',
message: '',
buttons,
});
}
};

View file

@ -165,6 +165,28 @@ describe('unit - DeepLinkSchemaMatch', function () {
},
],
},
{
argument: {
url: 'bluewallet:setelectrumserver?server=electrum1.bluewallet.io%3A443%3As',
},
expected: [
'ElectrumSettings',
{
server: 'electrum1.bluewallet.io:443:s',
},
],
},
{
argument: {
url: 'bluewallet:setlndhuburl?url=https%3A%2F%2Flndhub.herokuapp.com',
},
expected: [
'LightningSettings',
{
url: 'https://lndhub.herokuapp.com',
},
],
},
{
argument: {
url:
@ -291,4 +313,39 @@ describe('unit - DeepLinkSchemaMatch', function () {
assert.ok(DeeplinkSchemaMatch.isPossiblyPSBTFile('content://com.android.externalstorage.documents/document/081D-1403%3Atxhex.psbt'));
assert.ok(DeeplinkSchemaMatch.isPossiblyPSBTFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex.psbt'));
});
it('can work with some deeplink actions', () => {
assert.strictEqual(DeeplinkSchemaMatch.getServerFromSetElectrumServerAction('sgasdgasdgasd'), false);
assert.strictEqual(
DeeplinkSchemaMatch.getServerFromSetElectrumServerAction('bluewallet:setelectrumserver?server=electrum1.bluewallet.io%3A443%3As'),
'electrum1.bluewallet.io:443:s',
);
assert.strictEqual(
DeeplinkSchemaMatch.getServerFromSetElectrumServerAction('setelectrumserver?server=electrum1.bluewallet.io%3A443%3As'),
'electrum1.bluewallet.io:443:s',
);
assert.strictEqual(
DeeplinkSchemaMatch.getServerFromSetElectrumServerAction('ololo:setelectrumserver?server=electrum1.bluewallet.io%3A443%3As'),
false,
);
assert.strictEqual(
DeeplinkSchemaMatch.getServerFromSetElectrumServerAction('setTrololo?server=electrum1.bluewallet.io%3A443%3As'),
false,
);
assert.strictEqual(
DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction('bluewallet:setlndhuburl?url=https%3A%2F%2Flndhub.herokuapp.com'),
'https://lndhub.herokuapp.com',
);
assert.strictEqual(
DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction('bluewallet:setlndhuburl?url=https%3A%2F%2Flndhub.herokuapp.com%3A443'),
'https://lndhub.herokuapp.com:443',
);
assert.strictEqual(
DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction('setlndhuburl?url=https%3A%2F%2Flndhub.herokuapp.com%3A443'),
'https://lndhub.herokuapp.com:443',
);
assert.strictEqual(DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction('gsom?url=https%3A%2F%2Flndhub.herokuapp.com%3A443'), false);
assert.strictEqual(DeeplinkSchemaMatch.getUrlFromSetLndhubUrlAction('sdfhserhsthsd'), false);
});
});