Merge branch 'master' into transactionstatushook

This commit is contained in:
marcosrdz 2020-12-02 00:15:53 -05:00
commit 9e28aebfb4
29 changed files with 671 additions and 705 deletions

View file

@ -8,7 +8,6 @@ import {
Alert,
Animated,
Dimensions,
FlatList,
Image,
InputAccessoryView,
Keyboard,
@ -458,17 +457,15 @@ export class BlueWalletNavigationHeader extends Component {
}
}
export const BlueButtonLinkHook = props => {
export const BlueButtonLink = props => {
const { colors } = useTheme();
return (
<TouchableOpacity
style={{
minHeight: 60,
minWidth: 100,
height: 60,
justifyContent: 'center',
}}
onPress={props.onPress}
{...props}
>
<Text style={{ color: colors.foregroundColor, textAlign: 'center', fontSize: 16 }}>{props.title}</Text>
@ -476,23 +473,6 @@ export const BlueButtonLinkHook = props => {
);
};
export class BlueButtonLink extends Component {
render() {
return (
<TouchableOpacity
style={{
minHeight: 60,
minWidth: 100,
justifyContent: 'center',
}}
{...this.props}
>
<Text style={{ color: BlueCurrentTheme.colors.foregroundColor, textAlign: 'center', fontSize: 16 }}>{this.props.title}</Text>
</TouchableOpacity>
);
}
}
export const BlueAlertWalletExportReminder = ({ onSuccess = () => {}, onFailure }) => {
Alert.alert(
loc.wallets.details_title,
@ -546,6 +526,8 @@ export const BlueNavigationStyle = (navigation, withNavigationCloseButton = fals
};
export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuButton = false, advancedOptionsMenuButtonAction) => {
const { colors, closeImage } = useTheme();
let headerRight;
if (withAdvancedOptionsMenuButton) {
headerRight = () => (
@ -554,7 +536,7 @@ export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuB
onPress={advancedOptionsMenuButtonAction}
testID="advancedOptionsMenuButton"
>
<Icon size={22} name="kebab-horizontal" type="octicon" color={BlueCurrentTheme.colors.foregroundColor} />
<Icon size={22} name="kebab-horizontal" type="octicon" color={colors.foregroundColor} />
</TouchableOpacity>
);
} else {
@ -568,9 +550,9 @@ export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuB
},
headerTitleStyle: {
fontWeight: '600',
color: BlueCurrentTheme.colors.foregroundColor,
color: colors.foregroundColor,
},
headerTintColor: BlueCurrentTheme.colors.foregroundColor,
headerTintColor: colors.foregroundColor,
headerLeft: () => (
<TouchableOpacity
style={{ minWidth: 40, height: 40, justifyContent: 'center', paddingHorizontal: 14 }}
@ -579,7 +561,7 @@ export const BlueCreateTxNavigationStyle = (navigation, withAdvancedOptionsMenuB
navigation.goBack(null);
}}
>
<Image style={{}} source={BlueCurrentTheme.closeImage} />
<Image style={{}} source={closeImage} />
</TouchableOpacity>
),
headerRight,
@ -675,11 +657,9 @@ export const SafeBlueArea = props => {
return <SafeAreaView forceInset={{ horizontal: 'always' }} style={{ flex: 1, backgroundColor: colors.background }} {...props} />;
};
export class BlueCard extends Component {
render() {
return <View {...this.props} style={{ padding: 20 }} />;
}
}
export const BlueCard = props => {
return <View {...props} style={{ padding: 20 }} />;
};
export const BlueText = props => {
const { colors } = useTheme();
@ -760,122 +740,111 @@ export const BlueFormLabel = props => {
return <Text {...props} style={{ color: colors.foregroundColor, fontWeight: '400', marginHorizontal: 20 }} />;
};
export class BlueFormInput extends Component {
render() {
return (
<Input
{...this.props}
inputStyle={{ color: BlueCurrentTheme.colors.foregroundColor, maxWidth: width - 105 }}
containerStyle={{
marginTop: 5,
borderColor: BlueCurrentTheme.colors.inputBorderColor,
borderBottomColor: BlueCurrentTheme.colors.inputBorderColor,
borderWidth: 0.5,
borderBottomWidth: 0.5,
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
}}
/>
);
}
}
export const BlueFormInput = props => {
const { colors } = useTheme();
return (
<Input
{...props}
inputStyle={{ color: colors.foregroundColor, maxWidth: width - 105 }}
containerStyle={{
marginTop: 5,
borderColor: colors.inputBorderColor,
borderBottomColor: colors.inputBorderColor,
borderWidth: 0.5,
borderBottomWidth: 0.5,
backgroundColor: colors.inputBackgroundColor,
}}
/>
);
};
export class BlueFormMultiInput extends Component {
constructor(props) {
super(props);
this.state = {
selection: { start: 0, end: 0 },
};
}
export const BlueFormMultiInput = props => {
const { colors } = useTheme();
render() {
return (
<TextInput
multiline
underlineColorAndroid="transparent"
numberOfLines={4}
style={{
paddingHorizontal: 8,
paddingVertical: 16,
flex: 1,
marginTop: 5,
marginHorizontal: 20,
borderColor: BlueCurrentTheme.colors.formBorder,
borderBottomColor: BlueCurrentTheme.colors.formBorder,
borderWidth: 1,
borderBottomWidth: 0.5,
borderRadius: 4,
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
color: BlueCurrentTheme.colors.foregroundColor,
textAlignVertical: 'top',
}}
autoCorrect={false}
autoCapitalize="none"
spellCheck={false}
{...this.props}
selectTextOnFocus={false}
keyboardType={Platform.OS === 'android' ? 'visible-password' : 'default'}
/>
);
}
}
return (
<TextInput
multiline
underlineColorAndroid="transparent"
numberOfLines={4}
style={{
paddingHorizontal: 8,
paddingVertical: 16,
flex: 1,
marginTop: 5,
marginHorizontal: 20,
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
borderWidth: 1,
borderBottomWidth: 0.5,
borderRadius: 4,
backgroundColor: colors.inputBackgroundColor,
color: colors.foregroundColor,
textAlignVertical: 'top',
}}
autoCorrect={false}
autoCapitalize="none"
spellCheck={false}
{...props}
selectTextOnFocus={false}
keyboardType={Platform.OS === 'android' ? 'visible-password' : 'default'}
/>
);
};
export class BlueHeader extends Component {
render() {
return (
export const BlueHeader = props => {
return (
<Header
{...props}
backgroundColor="transparent"
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
/>
);
};
export const BlueHeaderDefaultSub = props => {
const { colors } = useTheme();
return (
<SafeAreaView style={{ backgroundColor: colors.brandingColor }}>
<Header
{...this.props}
backgroundColor="transparent"
backgroundColor={colors.background}
leftContainerStyle={{ minWidth: '100%' }}
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
/>
);
}
}
export class BlueHeaderDefaultSub extends Component {
render() {
return (
<SafeAreaView style={{ backgroundColor: BlueCurrentTheme.colors.brandingColor }}>
<Header
backgroundColor={BlueCurrentTheme.colors.background}
leftContainerStyle={{ minWidth: '100%' }}
outerContainerStyles={{
borderBottomColor: 'transparent',
borderBottomWidth: 0,
}}
leftComponent={
<Text
adjustsFontSizeToFit
style={{
fontWeight: 'bold',
fontSize: 30,
color: BlueCurrentTheme.colors.foregroundColor,
}}
>
{this.props.leftText}
</Text>
}
rightComponent={
<TouchableOpacity
onPress={() => {
if (this.props.onClose) this.props.onClose();
}}
>
<View style={stylesBlueIcon.box}>
<View style={stylesBlueIcon.ballTransparrent}>
<Image source={require('./img/close.png')} />
</View>
leftComponent={
<Text
adjustsFontSizeToFit
style={{
fontWeight: 'bold',
fontSize: 30,
color: colors.foregroundColor,
}}
>
{props.leftText}
</Text>
}
rightComponent={
<TouchableOpacity
onPress={() => {
if (props.onClose) props.onClose();
}}
>
<View style={stylesBlueIcon.box}>
<View style={stylesBlueIcon.ballTransparrent}>
<Image source={require('./img/close.png')} />
</View>
</TouchableOpacity>
}
{...this.props}
/>
</SafeAreaView>
);
}
}
</View>
</TouchableOpacity>
}
{...props}
/>
</SafeAreaView>
);
};
export const BlueHeaderDefaultSubHooks = props => {
const { colors } = useTheme();
@ -938,11 +907,9 @@ export const BlueHeaderDefaultMain = props => {
);
};
export class BlueSpacing extends Component {
render() {
return <View {...this.props} style={{ height: 60 }} />;
}
}
export const BlueSpacing = props => {
return <View {...props} style={{ height: 60 }} />;
};
export const BlueSpacing40 = props => {
return <View {...props} style={{ height: 50 }} />;
@ -972,12 +939,6 @@ export const BlueSpacing10 = props => {
return <View {...props} style={{ height: 10, opacity: 0 }} />;
};
export class BlueList extends Component {
render() {
return <FlatList {...this.props} />;
}
}
export class BlueUseAllFundsButton extends Component {
static InputAccessoryViewID = 'useMaxInputAccessoryViewID';
static propTypes = {
@ -1110,19 +1071,9 @@ export class BlueDoneAndDismissKeyboardInputAccessory extends Component {
}
}
export class BlueLoading extends Component {
render() {
return (
<View style={{ flex: 1, paddingTop: 200 }} {...this.props}>
<ActivityIndicator />
</View>
);
}
}
export const BlueLoadingHook = () => {
export const BlueLoading = props => {
return (
<View style={{ flex: 1, paddingTop: 200 }}>
<View style={{ flex: 1, paddingTop: 200 }} {...props}>
<ActivityIndicator />
</View>
);
@ -1421,93 +1372,6 @@ export const BlueReceiveButtonIcon = props => {
);
};
export class BlueScanButton extends Component {
render() {
return (
<TouchableOpacity {...this.props} style={{ flex: 1 }}>
<View
style={{
flex: 1,
minWidth: 130,
backgroundColor: BlueCurrentTheme.colors.buttonBackgroundColor,
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<View
style={{
minWidth: 24,
minHeight: 30,
backgroundColor: 'transparent',
alignItems: 'center',
marginBottom: -15,
marginLeft: -8,
}}
>
<Image resizeMode="stretch" source={BlueCurrentTheme.scanImage} />
</View>
<Text
style={{
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: sendReceiveScanButtonFontSize,
fontWeight: '600',
left: 5,
backgroundColor: 'transparent',
}}
>
{loc.send.details_scan}
</Text>
</View>
</View>
</TouchableOpacity>
);
}
}
export class BlueSendButtonIcon extends Component {
render() {
return (
<TouchableOpacity {...this.props} testID="SendButton" style={{ flex: 1 }}>
<View
style={{
flex: 1,
backgroundColor: BlueCurrentTheme.colors.buttonBackgroundColor,
alignItems: 'center',
}}
>
<View style={{ flex: 1, flexDirection: 'row', alignItems: 'center' }}>
<View
style={{
left: 5,
backgroundColor: 'transparent',
transform: [{ rotate: '225deg' }],
marginRight: 8,
}}
>
<Icon
{...this.props}
name="arrow-down"
size={sendReceiveScanButtonFontSize}
type="font-awesome"
color={BlueCurrentTheme.colors.buttonAlternativeTextColor}
/>
</View>
<Text
style={{
color: BlueCurrentTheme.colors.buttonAlternativeTextColor,
fontSize: sendReceiveScanButtonFontSize,
fontWeight: '500',
backgroundColor: 'transparent',
}}
>
{loc.send.header}
</Text>
</View>
</View>
</TouchableOpacity>
);
}
}
export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC, timeElapsed }) => {
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
const { colors } = useTheme();

View file

@ -75,6 +75,7 @@ import DrawerList from './screen/wallets/drawerList';
import { isTablet } from 'react-native-device-info';
import SettingsPrivacy from './screen/settings/SettingsPrivacy';
import LNDViewAdditionalInvoicePreImage from './screen/lnd/lndViewAdditionalInvoicePreImage';
import PsbtMultisigQRCode from './screen/send/psbtMultisigQRCode';
const defaultScreenOptions =
Platform.OS === 'ios'
@ -182,6 +183,7 @@ const SendDetailsRoot = () => (
/>
<SendDetailsStack.Screen name="CreateTransaction" component={SendCreate} options={SendCreate.navigationOptions} />
<SendDetailsStack.Screen name="PsbtMultisig" component={PsbtMultisig} options={PsbtMultisig.navigationOptions} />
<SendDetailsStack.Screen name="PsbtMultisigQRCode" component={PsbtMultisigQRCode} options={PsbtMultisigQRCode.navigationOptions} />
<SendDetailsStack.Screen
name="Success"
component={Success}

View file

@ -136,7 +136,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "5.6.7"
versionName "5.6.8"
multiDexEnabled true
missingDimensionStrategy 'react-native-camera', 'general'
testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type

View file

@ -1,7 +1,7 @@
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
import React, { Component } from 'react';
import { Text } from 'react-native-elements';
import { Dimensions, StyleSheet, TouchableOpacity, View } from 'react-native';
import { Dimensions, LayoutAnimation, StyleSheet, TouchableOpacity, View } from 'react-native';
import { encodeUR } from 'bc-ur/dist';
import QRCode from 'react-native-qrcode-svg';
import { BlueCurrentTheme } from '../components/themes';
@ -107,6 +107,7 @@ export class DynamicQRCode extends Component {
<TouchableOpacity
style={animatedQRCodeStyle.qrcodeContainer}
onPress={() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState(prevState => ({ hideControls: !prevState.hideControls }));
}}
>

File diff suppressed because one or more lines are too long

View file

@ -1513,7 +1513,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1556,7 +1556,7 @@
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@ -1597,7 +1597,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
@ -1636,7 +1636,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension;
PRODUCT_NAME = "BlueWallet - Bitcoin Price";
@ -1675,7 +1675,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget;
@ -1717,7 +1717,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1757,7 +1757,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
@ -1800,7 +1800,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1841,7 +1841,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationAndMarketWidget;
@ -1885,7 +1885,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationAndMarketWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -1926,7 +1926,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationWidget;
@ -1969,7 +1969,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -2111,7 +2111,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
@ -2151,7 +2151,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension;
PRODUCT_NAME = "${TARGET_NAME}";
@ -2185,7 +2185,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
IBSC_MODULE = BlueWalletWatch_Extension;
INFOPLIST_FILE = BlueWalletWatch/Info.plist;
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
@ -2221,7 +2221,7 @@
GCC_C_LANGUAGE_STANDARD = gnu11;
IBSC_MODULE = BlueWalletWatch_Extension;
INFOPLIST_FILE = BlueWalletWatch/Info.plist;
MARKETING_VERSION = 5.6.7;
MARKETING_VERSION = 5.6.8;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch;
PRODUCT_NAME = "$(TARGET_NAME)";

View file

@ -1,3 +1,26 @@
v5.6.7
======
* ADD: coincontrol
* ADD: Handle fiat rate from alternate sources
* ADD: new languages: Bulgarian, Polish, Welsh
* ADD: UYU currency
* FIX: PayJoin is now BIP compliant
* FIX: better support for BRD (aka bread) wallet with segwit
* FIX: Disregarding curent denomination on send screen, scanning address always resets it to BTC
* FIX: import *.txn file with txhex - extra newline characted prevented it from being recognized (closes #2161)
* FIX: locale pt_BR, cs_CZ, sl_SI, es_ES, nl_NL, fi_FI, ru
* FIX: translate message if Bitcoin address or LN invoice is in clipboard
* FIX: Styling for large screens
* FIX: exclude change address from recipients for Confirm screen
* FIX: Dont show loading indicator on launch and onsnapitem
* FIX: Show alert if storage access is denied
* FIX: When wallet card has balance but no txs it displays 'pull to refresh'
* FIX: broken wallet->send->longtap send btn->choose photo
* FIX: Use system color on widgets
* FIX: hide provide entropy button when creating Lightning or MS wallet
* FIX: Can't paste in address block while building tx
v5.6.6
======
@ -64,12 +87,3 @@ v5.6.1
* FIX: locales pt_BR, pt_PT, ru, sl_SI, ja_JP
* FIX: add margin for RTL languages
* FIX: Missing (NT) before $ sign
v.5.6.0
=======
* FIX: some transactions displayed with 0 value
* FIX: PSBT with HW wallets flow
* FIX: rare crash on watch-only receive button
* FIX: RBF cancel style
* REF: updated languages sl_SI, de_DE, fi_FI, es_ES

View file

@ -314,6 +314,7 @@
"details_inputs": "Inputs",
"details_outputs": "Outputs",
"details_received": "Received",
"transaction_note_saved":"Transaction note has been successfully saved.",
"details_show_in_block_explorer": "View in block explorer",
"details_title": "Transaction",
"details_to": "Output",

View file

@ -107,12 +107,13 @@
"lndViewInvoice": {
"additional_info": "Dodatne Informacije",
"for": "Za:",
"lightning_invoice": "Lightning Račun",
"has_been_paid": "Ta račun je bil plačan",
"open_direct_channel": "Odpri neposreden kanal s tem vozliščem:",
"please_pay": "Prosim plačajte",
"preimage": "Preimage",
"sats": "sats",
"wasnt_paid_and_expired": "Ta račun ni bil plačan in je potekel"
"wasnt_paid_and_expired": "Ta račun ni bil plačan in je potekel."
},
"plausibledeniability": {
"create_fake_storage": "Ustvari Šifrirano shrambo",
@ -358,7 +359,7 @@
"details_display": "prikaži na seznamu denarnic",
"details_export_backup": "Izvozi / varnostna kopija",
"details_marketplace": "Tržnica",
"details_master_fingerprint": "Master fingerprint",
"details_master_fingerprint": "Glavni prstni odtis (fingerprint)",
"details_no_cancel": "Ne, prekliči",
"details_save": "Shrani",
"details_show_xpub": "Prikaži XPUB denarnice",
@ -455,6 +456,10 @@
"view_edit_cosigners": "Prikaži/uredi sopodpisnike",
"this_cosigner_is_already_imported": "Ta sopodpisnik je že uvožen",
"export_signed_psbt": "Izvozi podpisano PSBT",
"input_fp": "Vnesite xfp (master key fingerprint)",
"input_fp_explain": "preskoči in uporabi privzetega (00000000)",
"input_path": "Vnesite pot izpeljave (derivation path)",
"input_path_explain": "preskoči in uporabi privzeto ({default})",
"view_edit_cosigners_title": "Urejanje sopodpisnikov"
},
"cc": {

39
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "bluewallet",
"version": "5.6.7",
"version": "5.6.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -8306,12 +8306,19 @@
}
},
"buffer": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.1.tgz",
"integrity": "sha512-rVAXBwEcEoYtxnHSO5iWyhzV/O1WMtkUYWlfdLS7FjU4PnSJJHEfHXi/uHPI5EwltmOA794gN3bm3/pzuctWjQ==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
},
"dependencies": {
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
}
}
},
"buffer-alloc": {
@ -18304,6 +18311,15 @@
"yargs": "^13.2.4"
},
"dependencies": {
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@ -19184,6 +19200,17 @@
"integrity": "sha512-ToyFSYuJp0/DQ7bXd/vTwF5m3yhNSRngIpVlBFBDccDZQmp2qIo0exrObCNlJLOOHb38dil726hM+GKjR1/60w==",
"requires": {
"buffer": "^5.4.3"
},
"dependencies": {
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
}
}
},
"react-native-tooltip": {

View file

@ -1,6 +1,6 @@
{
"name": "bluewallet",
"version": "5.6.7",
"version": "5.6.8",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.10.4",
@ -90,7 +90,7 @@
"bip39": "2.6.0",
"bitcoinjs-lib": "5.2.0",
"bolt11": "1.2.7",
"buffer": "5.6.0",
"buffer": "6.0.1",
"buffer-reverse": "1.0.1",
"coinselect": "3.1.12",
"crypto-js": "3.1.9-1",

View file

@ -16,11 +16,11 @@ import Share from 'react-native-share';
import Handoff from 'react-native-handoff';
import {
BlueLoadingHook,
BlueLoading,
BlueCopyTextToClipboard,
BlueButton,
SecondButton,
BlueButtonLinkHook,
BlueButtonLink,
is,
BlueBitcoinAmount,
BlueText,
@ -161,7 +161,7 @@ const ReceiveDetails = () => {
<BlueCopyTextToClipboard text={isCustom ? bip21encoded : address} />
</View>
<View style={styles.share}>
<BlueButtonLinkHook title={loc.receive.details_setAmount} onPress={showCustomAmountModal} />
<BlueButtonLink title={loc.receive.details_setAmount} onPress={showCustomAmountModal} />
<View>
<SecondButton onPress={handleShareButtonPressed} title={loc.receive.details_share} />
</View>
@ -345,7 +345,7 @@ const ReceiveDetails = () => {
url={`https://blockstream.info/address/${address}`}
/>
)}
{showAddress ? renderReceiveDetails() : <BlueLoadingHook />}
{showAddress ? renderReceiveDetails() : <BlueLoading />}
</View>
);
};

View file

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { ScrollView, View, StyleSheet } from 'react-native';
import { BlueSpacing20, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle, BlueLoadingHook } from '../BlueComponents';
import { BlueSpacing20, SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle, BlueLoading } from '../BlueComponents';
import PropTypes from 'prop-types';
import { SegwitP2SHWallet, LegacyWallet, HDSegwitP2SHWallet, HDSegwitBech32Wallet } from '../class';
import { BlueCurrentTheme } from '../components/themes';
@ -210,7 +210,7 @@ export default class Selftest extends Component {
render() {
if (this.state.isLoading) {
return <BlueLoadingHook />;
return <BlueLoading />;
}
return (

View file

@ -7,7 +7,7 @@ import ImagePicker from 'react-native-image-picker';
import { decodeUR, extractSingleWorkload } from 'bc-ur';
import { useNavigation, useRoute, useIsFocused, useTheme } from '@react-navigation/native';
import loc from '../../loc';
import { BlueLoadingHook, BlueText, BlueButton, BlueSpacing40 } from '../../BlueComponents';
import { BlueLoading, BlueText, BlueButton, BlueSpacing40 } from '../../BlueComponents';
import { BlueCurrentTheme } from '../../components/themes';
import { openPrivacyDesktopSettings } from '../../class/camera';
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
@ -231,7 +231,7 @@ const ScanQRCode = () => {
return isLoading ? (
<View style={styles.root}>
<BlueLoadingHook />
<BlueLoading />
</View>
) : (
<View style={styles.root}>

View file

@ -647,7 +647,7 @@ export default class SendDetails extends Component {
this.props.navigation.navigate('PsbtMultisig', {
memo: this.state.memo,
psbtBase64: psbt.toBase64(),
walletId: wallet.getID(),
walletID: wallet.getID(),
});
this.setState({ isLoading: false });
return;
@ -976,7 +976,7 @@ export default class SendDetails extends Component {
this.props.navigation.navigate('PsbtMultisig', {
memo: this.state.memo,
psbtBase64: psbt.toBase64(),
walletId: fromWallet.getID(),
walletID: fromWallet.getID(),
});
} catch (error) {
alert(loc.send.problem_with_psbt + ': ' + error.message);

View file

@ -1,22 +1,14 @@
/* global alert */
import React, { useContext, useState } from 'react';
import { FlatList, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { BlueButton, BlueButtonLink, BlueCard, BlueNavigationStyle, BlueSpacing20, BlueText, SafeBlueArea } from '../../BlueComponents';
import { DynamicQRCode } from '../../components/DynamicQRCode';
import { SquareButton } from '../../components/SquareButton';
import { getSystemName } from 'react-native-device-info';
import React, { useContext, useEffect, useState } from 'react';
import { FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { BlueButton, BlueCard, BlueNavigationStyle, BlueText, SafeBlueArea } from '../../BlueComponents';
import loc from '../../loc';
import { Icon } from 'react-native-elements';
import ImagePicker from 'react-native-image-picker';
import ScanQRCode from './ScanQRCode';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BitcoinUnit } from '../../models/bitcoinUnits';
import { BlueStorageContext } from '../../blue_modules/storage-context';
const bitcoin = require('bitcoinjs-lib');
const currency = require('../../blue_modules/currency');
const fs = require('../../blue_modules/fs');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const isDesktop = getSystemName() === 'Mac OS X';
const BigNumber = require('bignumber.js');
const shortenAddress = addr => {
@ -25,21 +17,21 @@ const shortenAddress = addr => {
const PsbtMultisig = () => {
const { wallets } = useContext(BlueStorageContext);
const navigation = useNavigation();
const route = useRoute();
const { navigate, setParams } = useNavigation();
const { colors } = useTheme();
const [flatListHeight, setFlatListHeight] = useState(0);
const walletId = route.params.walletId;
const psbtBase64 = route.params.psbtBase64;
const memo = route.params.memo;
const { walletID, psbtBase64, memo, receivedPSBTBase64 } = useRoute().params;
/** @type MultisigHDWallet */
const wallet = wallets.find(w => w.getID() === walletID);
const [psbt, setPsbt] = useState(bitcoin.Psbt.fromBase64(psbtBase64));
const [isModalVisible, setIsModalVisible] = useState(false);
const data = new Array(wallet.getM());
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.elevated,
},
whitespace: {
color: colors.elevated,
},
textBtc: {
color: colors.buttonAlternativeTextColor,
},
@ -52,18 +44,12 @@ const PsbtMultisig = () => {
textDestination: {
color: colors.foregroundColor,
},
modalContentShort: {
backgroundColor: colors.elevated,
},
textFiat: {
color: colors.alternativeTextColor,
},
provideSignatureButton: {
backgroundColor: colors.buttonDisabledBackgroundColor,
},
exportButton: {
backgroundColor: colors.buttonDisabledBackgroundColor,
},
provideSignatureButtonText: {
color: colors.buttonTextColor,
},
@ -83,8 +69,7 @@ const PsbtMultisig = () => {
color: colors.msSuccessBG,
},
});
/** @type MultisigHDWallet */
const wallet = wallets.find(w => w.getID() === walletId);
let destination = [];
let totalSat = 0;
const targets = [];
@ -98,23 +83,22 @@ const PsbtMultisig = () => {
destination = shortenAddress(destination.join(', '));
const totalBtc = new BigNumber(totalSat).dividedBy(100000000).toNumber();
const totalFiat = currency.satoshiToLocalCurrency(totalSat);
const fileName = `${Date.now()}.psbt`;
const howManySignaturesWeHave = () => {
return wallet.calculateHowManySignaturesWeHaveFromPsbt(psbt);
};
const getFee = () => {
return wallet.calculateFeeFromPsbt(psbt);
};
const _renderItem = el => {
if (el.index >= howManySignaturesWeHave()) return _renderItemUnsigned(el);
if (el.index >= howManySignaturesWeHave) return _renderItemUnsigned(el);
else return _renderItemSigned(el);
};
const navigateToPSBTMultisigQRCode = () => {
navigate('PsbtMultisigQRCode', { walletID, psbtBase64, isShowOpenScanner: isConfirmEnabled() });
};
const _renderItemUnsigned = el => {
const renderProvideSignature = el.index === howManySignaturesWeHave();
const renderProvideSignature = el.index === howManySignaturesWeHave;
return (
<View testID="ItemUnsigned">
<View style={styles.itemUnsignedWrapper}>
@ -133,9 +117,7 @@ const PsbtMultisig = () => {
<TouchableOpacity
testID="ProvideSignature"
style={[styles.provideSignatureButton, stylesHook.provideSignatureButton]}
onPress={() => {
setIsModalVisible(true);
}}
onPress={navigateToPSBTMultisigQRCode}
>
<Text style={[styles.provideSignatureButtonText, stylesHook.provideSignatureButtonText]}>
{loc.multisig.provide_signature}
@ -162,31 +144,25 @@ const PsbtMultisig = () => {
);
};
const _combinePSBT = receivedPSBTBase64 => {
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
if (receivedPSBTBase64) {
_combinePSBT();
setParams({ receivedPSBTBase64: undefined });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [receivedPSBTBase64]);
const _combinePSBT = () => {
const receivedPSBT = bitcoin.Psbt.fromBase64(receivedPSBTBase64);
try {
const newPsbt = psbt.combine(receivedPSBT);
navigation.dangerouslyGetParent().pop();
setPsbt(newPsbt);
setIsModalVisible(false);
} catch (error) {
alert(error);
}
};
const onBarScanned = ret => {
if (!ret.data) ret = { data: ret };
if (ret.data.toUpperCase().startsWith('UR')) {
alert('BC-UR not decoded. This should never happen');
} else if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
// this looks like NOT base64, so maybe its transaction's hex
// we dont support it in this flow
} else {
// psbt base64?
_combinePSBT(ret.data);
}
};
const onConfirm = () => {
try {
psbt.finalizeAllInputs();
@ -195,7 +171,7 @@ const PsbtMultisig = () => {
try {
const tx = psbt.extractTransaction().toHex();
const satoshiPerByte = Math.round(getFee() / (tx.length / 2));
navigation.navigate('Confirm', {
navigate('Confirm', {
fee: new BigNumber(getFee()).dividedBy(100000000).toNumber(),
memo: memo,
fromWallet: wallet,
@ -208,83 +184,20 @@ const PsbtMultisig = () => {
}
};
const openScanner = () => {
if (isDesktop) {
ImagePicker.launchCamera(
{
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) {
onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
} else if (response.error) {
ScanQRCode.presentCameraNotAuthorizedAlert(response.error);
}
},
);
} else {
navigation.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
onBarScanned: onBarScanned,
showFileImportButton: true,
},
});
}
};
const exportPSBT = async () => {
await fs.writeFileAndExport(fileName, psbt.toBase64());
};
const howManySignaturesWeHave = wallet.calculateHowManySignaturesWeHaveFromPsbt(psbt);
const isConfirmEnabled = () => {
return howManySignaturesWeHave() >= wallet.getM();
};
const renderDynamicQrCode = () => {
return (
<SafeBlueArea style={[styles.root, stylesHook.root]}>
<ScrollView centerContent contentContainerStyle={styles.scrollViewContent}>
<View style={[styles.modalContentShort, stylesHook.modalContentShort]}>
<DynamicQRCode value={psbt.toHex()} capacity={666} />
{!isConfirmEnabled() && (
<>
<BlueSpacing20 />
<SquareButton
testID="CosignedScanOrImportFile"
style={[styles.exportButton, stylesHook.exportButton]}
onPress={openScanner}
title={loc.multisig.scan_or_import_file}
/>
</>
)}
<BlueSpacing20 />
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportPSBT} title={loc.multisig.share} />
<BlueSpacing20 />
<BlueButtonLink title={loc._.cancel} onPress={() => setIsModalVisible(false)} />
</View>
</ScrollView>
</SafeBlueArea>
);
return howManySignaturesWeHave >= wallet.getM();
};
const destinationAddress = () => {
// eslint-disable-next-line prefer-const
let destinationAddressView = [];
const whitespace = '_';
const destinations = Object.entries(destination.split(','));
for (const [index, address] of destinations) {
if (index > 1) {
destinationAddressView.push(
<View style={styles.destionationTextContainer} key={`end-${index}`}>
<View style={styles.destinationTextContainer} key={`end-${index}`}>
<Text numberOfLines={0} style={[styles.textDestinationFirstFour, stylesHook.textFiat]}>
and {destinations.length - 2} more...
</Text>
@ -297,13 +210,15 @@ const PsbtMultisig = () => {
const lastFour = currentAddress.substring(currentAddress.length - 5, currentAddress.length);
const middle = currentAddress.split(firstFour)[1].split(lastFour)[0];
destinationAddressView.push(
<View style={styles.destionationTextContainer} key={`${currentAddress}-${index}`}>
<Text numberOfLines={2} style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>
{firstFour}
<Text> </Text>
<Text style={[styles.textDestination, stylesHook.textFiat]}>{middle}</Text>
<Text> </Text>
<Text style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>{lastFour}</Text>
<View style={styles.destinationTextContainer} key={`${currentAddress}-${index}`}>
<Text style={styles.textAlignCenter}>
<Text numberOfLines={2} style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>
{firstFour}
<Text style={stylesHook.whitespace}>{whitespace}</Text>
<Text style={[styles.textDestination, stylesHook.textFiat]}>{middle}</Text>
<Text style={stylesHook.whitespace}>{whitespace}</Text>
<Text style={[styles.textDestinationFirstFour, stylesHook.textBtc]}>{lastFour}</Text>
</Text>
</Text>
</View>,
);
@ -338,13 +253,10 @@ const PsbtMultisig = () => {
</View>
);
if (isModalVisible) return renderDynamicQrCode();
const onLayout = e => {
setFlatListHeight(e.nativeEvent.layout.height);
};
const data = new Array(wallet.getM());
return (
<SafeBlueArea style={[styles.root, stylesHook.root]}>
<View style={styles.container}>
@ -367,9 +279,7 @@ const PsbtMultisig = () => {
<TouchableOpacity
testID="ExportSignedPsbt"
style={[styles.provideSignatureButton, stylesHook.provideSignatureButton]}
onPress={() => {
setIsModalVisible(true);
}}
onPress={navigateToPSBTMultisigQRCode}
>
<Text style={[styles.provideSignatureButtonText, stylesHook.provideSignatureButtonText]}>
{loc.multisig.export_signed_psbt}
@ -422,7 +332,7 @@ const styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'center',
},
destionationTextContainer: {
destinationTextContainer: {
flexDirection: 'row',
marginBottom: 4,
paddingHorizontal: 60,
@ -438,6 +348,9 @@ const styles = StyleSheet.create({
fontWeight: 'bold',
fontSize: 30,
},
textAlignCenter: {
textAlign: 'center',
},
textDestinationFirstFour: {
fontSize: 14,
},
@ -447,21 +360,6 @@ const styles = StyleSheet.create({
fontSize: 14,
flexWrap: 'wrap',
},
modalContentShort: {
marginLeft: 20,
marginRight: 20,
},
copyToClipboard: {
justifyContent: 'center',
alignItems: 'center',
},
exportButton: {
height: 48,
borderRadius: 8,
flex: 1,
justifyContent: 'center',
paddingHorizontal: 16,
},
provideSignatureButton: {
marginTop: 24,
marginLeft: 40,

View file

@ -0,0 +1,147 @@
/* global alert */
import React, { useState } from 'react';
import { ActivityIndicator, Platform, ScrollView, StyleSheet, View } from 'react-native';
import { BlueNavigationStyle, BlueSpacing20, SafeBlueArea } from '../../BlueComponents';
import { DynamicQRCode } from '../../components/DynamicQRCode';
import { SquareButton } from '../../components/SquareButton';
import { getSystemName } from 'react-native-device-info';
import loc from '../../loc';
import ImagePicker from 'react-native-image-picker';
import ScanQRCode from './ScanQRCode';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
const bitcoin = require('bitcoinjs-lib');
const fs = require('../../blue_modules/fs');
const LocalQRCode = require('@remobile/react-native-qrcode-local-image');
const isDesktop = getSystemName() === 'Mac OS X';
const PsbtMultisigQRCode = () => {
const { navigate } = useNavigation();
const { colors } = useTheme();
const { psbtBase64, isShowOpenScanner } = useRoute().params;
const [isLoading, setIsLoading] = useState(false);
const psbt = bitcoin.Psbt.fromBase64(psbtBase64);
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.elevated,
},
modalContentShort: {
backgroundColor: colors.elevated,
},
exportButton: {
backgroundColor: colors.buttonDisabledBackgroundColor,
},
});
const fileName = `${Date.now()}.psbt`;
const onBarScanned = ret => {
if (!ret.data) ret = { data: ret };
if (ret.data.toUpperCase().startsWith('UR')) {
alert('BC-UR not decoded. This should never happen');
} else if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
// this looks like NOT base64, so maybe its transaction's hex
// we dont support it in this flow
} else {
// psbt base64?
navigate('PsbtMultisig', { receivedPSBTBase64: ret.data });
}
};
const openScanner = () => {
if (isDesktop) {
ImagePicker.launchCamera(
{
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) {
onBarScanned(result);
} else {
alert(loc.send.qr_error_no_qrcode);
}
});
} else if (response.error) {
ScanQRCode.presentCameraNotAuthorizedAlert(response.error);
}
},
);
} else {
navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
onBarScanned: onBarScanned,
showFileImportButton: true,
},
});
}
};
const exportPSBT = () => {
setIsLoading(true);
setTimeout(() => fs.writeFileAndExport(fileName, psbt.toBase64()).finally(() => setIsLoading(false)), 10);
};
return (
<SafeBlueArea style={[styles.root, stylesHook.root]}>
<ScrollView centerContent contentContainerStyle={styles.scrollViewContent}>
<View style={[styles.modalContentShort, stylesHook.modalContentShort]}>
<DynamicQRCode value={psbt.toHex()} capacity={666} />
{!isShowOpenScanner && (
<>
<BlueSpacing20 />
<SquareButton
testID="CosignedScanOrImportFile"
style={[styles.exportButton, stylesHook.exportButton]}
onPress={openScanner}
title={loc.multisig.scan_or_import_file}
/>
</>
)}
<BlueSpacing20 />
{isLoading ? (
<ActivityIndicator />
) : (
<SquareButton style={[styles.exportButton, stylesHook.exportButton]} onPress={exportPSBT} title={loc.multisig.share} />
)}
</View>
</ScrollView>
</SafeBlueArea>
);
};
const styles = StyleSheet.create({
root: {
flex: 1,
},
scrollViewContent: {
flexGrow: 1,
justifyContent: 'space-between',
},
modalContentShort: {
marginLeft: 20,
marginRight: 20,
},
copyToClipboard: {
justifyContent: 'center',
alignItems: 'center',
},
exportButton: {
height: 48,
borderRadius: 8,
flex: 1,
justifyContent: 'center',
paddingHorizontal: 16,
},
});
PsbtMultisigQRCode.navigationOptions = () => ({
...BlueNavigationStyle(null, false),
title: loc.multisig.header,
});
export default PsbtMultisigQRCode;

View file

@ -15,7 +15,7 @@ const Success = () => {
};
const { colors } = useTheme();
const { dangerouslyGetParent } = useNavigation();
const { amount = 0, fee = 0, amountUnit = BitcoinUnit.BTC, invoiceDescription = '', onDonePressed = pop } = useRoute().params;
const { amount, fee, amountUnit = BitcoinUnit.BTC, invoiceDescription = '', onDonePressed = pop } = useRoute().params;
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.elevated,
@ -76,10 +76,12 @@ export const SuccessView = ({ amount, amountUnit, fee, invoiceDescription, shoul
<View style={styles.root}>
<BlueCard style={styles.amount}>
<View style={styles.view}>
<>
<Text style={[styles.amountValue, stylesHook.amountValue]}>{amount}</Text>
<Text style={[styles.amountUnit, stylesHook.amountUnit]}>{' ' + amountUnit}</Text>
</>
{amount && (
<>
<Text style={[styles.amountValue, stylesHook.amountValue]}>{amount}</Text>
<Text style={[styles.amountUnit, stylesHook.amountUnit]}>{' ' + amountUnit}</Text>
</>
)}
</View>
{fee > 0 && (
<Text style={styles.feeText}>

View file

@ -4,7 +4,7 @@ import { ScrollView, Alert, Platform, TouchableOpacity, TouchableWithoutFeedback
import { useNavigation } from '@react-navigation/native';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import {
BlueLoadingHook,
BlueLoading,
SafeBlueArea,
BlueSpacing20,
BlueCard,
@ -149,7 +149,7 @@ const EncryptStorage = () => {
return isLoading ? (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
<BlueLoadingHook />
<BlueLoading />
</SafeBlueArea>
) : (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react';
import { FlatList, StyleSheet } from 'react-native';
import { SafeBlueArea, BlueListItem, BlueCard, BlueLoadingHook, BlueNavigationStyle, BlueText } from '../../BlueComponents';
import { SafeBlueArea, BlueListItem, BlueCard, BlueLoading, BlueNavigationStyle, BlueText } from '../../BlueComponents';
import { AvailableLanguages } from '../../loc/languages';
import loc from '../../loc';
@ -40,7 +40,7 @@ const Language = () => {
);
return isLoading ? (
<BlueLoadingHook />
<BlueLoading />
) : (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.flex}>
<FlatList style={styles.flex} keyExtractor={(_item, index) => `${index}`} data={AvailableLanguages} renderItem={renderItem} />

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle, BlueSpacing20, BlueLoadingHook } from '../../BlueComponents';
import { SafeBlueArea, BlueCard, BlueText, BlueNavigationStyle, BlueSpacing20, BlueLoading } from '../../BlueComponents';
/** @type {AppStorage} */
const styles = StyleSheet.create({
@ -17,7 +17,7 @@ const Licensing = () => {
}, []);
return isLoading ? (
<BlueLoadingHook />
<BlueLoading />
) : (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
<ScrollView>

View file

@ -11,7 +11,7 @@ import {
SafeBlueArea,
BlueCard,
BlueNavigationStyle,
BlueLoadingHook,
BlueLoading,
BlueText,
BlueButtonLink,
} from '../../BlueComponents';
@ -130,7 +130,7 @@ const LightningSettings = () => {
<BlueButtonLink title={loc.wallets.import_scan_qr} onPress={importScan} />
<BlueSpacing20 />
{isLoading ? <BlueLoadingHook /> : <BlueButton onPress={save} title={loc.settings.save} />}
{isLoading ? <BlueLoading /> : <BlueButton onPress={save} title={loc.settings.save} />}
</BlueCard>
</SafeBlueArea>
);

View file

@ -1,5 +1,5 @@
/* global alert */
import React, { Component } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { View, ScrollView, TouchableOpacity, Text, TextInput, Linking, StatusBar, StyleSheet, Keyboard } from 'react-native';
import {
SafeBlueArea,
@ -12,12 +12,227 @@ import {
} from '../../BlueComponents';
import HandoffSettings from '../../class/handoff';
import Handoff from 'react-native-handoff';
import PropTypes from 'prop-types';
import loc from '../../loc';
import { BlueCurrentTheme } from '../../components/themes';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
const dayjs = require('dayjs');
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
function arrDiff(a1, a2) {
const ret = [];
for (const v of a2) {
if (a1.indexOf(v) === -1) {
ret.push(v);
}
}
return ret;
}
const TransactionsDetails = () => {
const { setOptions } = useNavigation();
const { hash } = useRoute().params;
const { saveToDisk, txMetadata, wallets, getTransactions } = useContext(BlueStorageContext);
const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false);
const [from, setFrom] = useState();
const [to, setTo] = useState();
const [isLoading, setIsLoading] = useState(true);
const [tx, setTX] = useState();
const [memo, setMemo] = useState();
const { colors } = useTheme();
const stylesHooks = StyleSheet.create({
rowCaption: {
color: colors.foregroundColor,
},
txId: {
color: colors.foregroundColor,
},
txLink: {
color: colors.alternativeTextColor2,
},
saveText: {
color: colors.alternativeTextColor2,
},
memoTextInput: {
borderColor: colors.formBorder,
borderBottomColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
},
});
useEffect(() => {
setOptions({
headerRight: () => (
<TouchableOpacity disabled={isLoading} style={styles.save} onPress={handleOnSaveButtonTapped}>
<Text style={stylesHooks.saveText}>{loc.wallets.details_save}</Text>
</TouchableOpacity>
),
headerStyle: {
borderBottomWidth: 0,
elevation: 0,
shadowOpacity: 0,
shadowOffset: { height: 0, width: 0 },
backgroundColor: colors.customHeader,
},
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colors, isLoading, memo]);
useEffect(() => {
let foundTx = {};
let from = [];
let to = [];
for (const tx of getTransactions()) {
if (tx.hash === hash) {
foundTx = tx;
for (const input of foundTx.inputs) {
from = from.concat(input.addresses);
}
for (const output of foundTx.outputs) {
if (output.addresses) to = to.concat(output.addresses);
if (output.scriptPubKey && output.scriptPubKey.addresses) to = to.concat(output.scriptPubKey.addresses);
}
}
}
for (const w of wallets) {
for (const t of w.getTransactions()) {
if (t.hash === hash) {
console.log('tx', hash, 'belongs to', w.getLabel());
}
}
}
if (txMetadata[foundTx.hash]) {
if (txMetadata[foundTx.hash].memo) {
setMemo(txMetadata[foundTx.hash].memo);
}
}
setTX(foundTx);
setFrom(from);
setTo(to);
setIsLoading(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hash]);
useEffect(() => {
HandoffSettings.isHandoffUseEnabled().then(setIsHandOffUseEnabled);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleOnSaveButtonTapped = () => {
Keyboard.dismiss();
txMetadata[tx.hash] = { memo };
saveToDisk().then(_success => alert(loc.transactions.transaction_note_saved));
};
const handleOnOpenTransactionOnBlockExporerTapped = () => {
const url = `https://blockstream.info/tx/${tx.hash}`;
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
}
});
};
if (isLoading || !tx) {
return <BlueLoading />;
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
{isHandOffUseEnabled && (
<Handoff title={`Bitcoin Transaction ${tx.hash}`} type="io.bluewallet.bluewallet" url={`https://blockstream.info/tx/${tx.hash}`} />
)}
<StatusBar barStyle="default" />
<ScrollView style={styles.scroll}>
<BlueCard>
<View>
<TextInput
placeholder={loc.send.details_note_placeholder}
value={memo}
placeholderTextColor="#81868e"
style={[styles.memoTextInput, stylesHooks.memoTextInput]}
onChangeText={setMemo}
/>
<BlueSpacing20 />
</View>
{from && (
<>
<View style={styles.rowHeader}>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_from}</BlueText>
<BlueCopyToClipboardButton stringToCopy={from.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={styles.rowValue}>{from.filter(onlyUnique).join(', ')}</BlueText>
</>
)}
{to && (
<>
<View style={styles.rowHeader}>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_to}</BlueText>
<BlueCopyToClipboardButton stringToCopy={to.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={styles.rowValue}>{arrDiff(from, to.filter(onlyUnique)).join(', ')}</BlueText>
</>
)}
{tx.fee && (
<>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.send.create_fee}</BlueText>
<BlueText style={styles.rowValue}>{tx.fee + ' sats'}</BlueText>
</>
)}
{tx.hash && (
<>
<View style={styles.rowHeader}>
<BlueText style={[styles.txId, stylesHooks.txId]}>Txid</BlueText>
<BlueCopyToClipboardButton stringToCopy={tx.hash} />
</View>
<BlueText style={styles.txHash}>{tx.hash}</BlueText>
<TouchableOpacity onPress={handleOnOpenTransactionOnBlockExporerTapped}>
<BlueText style={[styles.txLink, stylesHooks.txLink]}>{loc.transactions.details_show_in_block_explorer}</BlueText>
</TouchableOpacity>
</>
)}
{tx.received && (
<>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_received}</BlueText>
<BlueText style={styles.rowValue}>{dayjs(tx.received).format('MM/DD/YYYY h:mm A')}</BlueText>
</>
)}
{tx.block_height > 0 && (
<>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_block}</BlueText>
<BlueText style={styles.rowValue}>{tx.block_height}</BlueText>
</>
)}
{tx.inputs && (
<>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_inputs}</BlueText>
<BlueText style={styles.rowValue}>{tx.inputs.length}</BlueText>
</>
)}
{tx.outputs.length > 0 && (
<>
<BlueText style={[styles.rowCaption, stylesHooks.rowCaption]}>{loc.transactions.details_outputs}</BlueText>
<BlueText style={styles.rowValue}>{tx.outputs.length}</BlueText>
</>
)}
</BlueCard>
</ScrollView>
</SafeBlueArea>
);
};
const styles = StyleSheet.create({
root: {
flex: 1,
@ -35,7 +250,6 @@ const styles = StyleSheet.create({
fontSize: 16,
fontWeight: '500',
marginBottom: 4,
color: BlueCurrentTheme.colors.foregroundColor,
},
rowValue: {
marginBottom: 26,
@ -44,7 +258,6 @@ const styles = StyleSheet.create({
txId: {
fontSize: 16,
fontWeight: '500',
color: BlueCurrentTheme.colors.foregroundColor,
},
txHash: {
marginBottom: 8,
@ -52,23 +265,16 @@ const styles = StyleSheet.create({
},
txLink: {
marginBottom: 26,
color: BlueCurrentTheme.colors.alternativeTextColor2,
},
save: {
marginHorizontal: 16,
justifyContent: 'center',
alignItems: 'center',
},
saveText: {
color: BlueCurrentTheme.colors.alternativeTextColor2,
},
memoTextInput: {
flexDirection: 'row',
borderColor: BlueCurrentTheme.colors.formBorder,
borderBottomColor: BlueCurrentTheme.colors.formBorder,
borderWidth: 1,
borderBottomWidth: 0.5,
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
minHeight: 44,
height: 44,
alignItems: 'center',
@ -79,221 +285,9 @@ const styles = StyleSheet.create({
},
});
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
export default TransactionsDetails;
function arrDiff(a1, a2) {
const ret = [];
for (const v of a2) {
if (a1.indexOf(v) === -1) {
ret.push(v);
}
}
return ret;
}
export default class TransactionsDetails extends Component {
static contextType = BlueStorageContext;
constructor(props, context) {
super(props);
const hash = props.route.params.hash;
let foundTx = {};
let from = [];
let to = [];
for (const tx of context.getTransactions()) {
if (tx.hash === hash) {
foundTx = tx;
for (const input of foundTx.inputs) {
from = from.concat(input.addresses);
}
for (const output of foundTx.outputs) {
if (output.addresses) to = to.concat(output.addresses);
if (output.scriptPubKey && output.scriptPubKey.addresses) to = to.concat(output.scriptPubKey.addresses);
}
}
}
let wallet = false;
for (const w of context.wallets) {
for (const t of w.getTransactions()) {
if (t.hash === hash) {
console.log('tx', hash, 'belongs to', w.getLabel());
wallet = w;
}
}
}
let memo = '';
if (context.txMetadata[foundTx.hash]) {
if (context.txMetadata[foundTx.hash].memo) {
memo = context.txMetadata[foundTx.hash].memo;
}
}
this.state = {
isLoading: true,
tx: foundTx,
from,
to,
wallet,
isHandOffUseEnabled: false,
memo,
};
}
async componentDidMount() {
console.log('transactions/details - componentDidMount');
this.props.navigation.setParams({ handleOnSaveButtonTapped: this.handleOnSaveButtonTapped });
const isHandOffUseEnabled = await HandoffSettings.isHandoffUseEnabled();
this.setState({
isLoading: false,
isHandOffUseEnabled,
});
}
handleOnSaveButtonTapped = () => {
Keyboard.dismiss();
this.context.txMetadata[this.state.tx.hash] = { memo: this.state.memo };
this.context.saveToDisk().then(_success => alert('Transaction note has been successfully saved.'));
};
handleOnMemoChangeText = value => {
this.setState({ memo: value });
};
render() {
if (this.state.isLoading || !('tx' in this.state)) {
return <BlueLoading />;
}
return (
<SafeBlueArea forceInset={{ horizontal: 'always' }} style={styles.root}>
{this.state.isHandOffUseEnabled && (
<Handoff
title={`Bitcoin Transaction ${this.state.tx.hash}`}
type="io.bluewallet.bluewallet"
url={`https://blockstream.info/tx/${this.state.tx.hash}`}
/>
)}
<StatusBar barStyle="default" />
<ScrollView style={styles.scroll}>
<BlueCard>
<View>
<TextInput
placeholder={loc.send.details_note_placeholder}
value={this.state.memo}
placeholderTextColor="#81868e"
style={styles.memoTextInput}
onChangeText={this.handleOnMemoChangeText}
/>
<BlueSpacing20 />
</View>
{'from' in this.state && (
<>
<View style={styles.rowHeader}>
<BlueText style={styles.rowCaption}>{loc.transactions.details_from}</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.from.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={styles.rowValue}>{this.state.from.filter(onlyUnique).join(', ')}</BlueText>
</>
)}
{'to' in this.state && (
<>
<View style={styles.rowHeader}>
<BlueText style={styles.rowCaption}>{loc.transactions.details_to}</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.to.filter(onlyUnique).join(', ')} />
</View>
<BlueText style={styles.rowValue}>{arrDiff(this.state.from, this.state.to.filter(onlyUnique)).join(', ')}</BlueText>
</>
)}
{'fee' in this.state.tx && (
<>
<BlueText style={styles.rowCaption}>{loc.send.create_fee}</BlueText>
<BlueText style={styles.rowValue}>{this.state.tx.fee + ' sats'}</BlueText>
</>
)}
{'hash' in this.state.tx && (
<>
<View style={styles.rowHeader}>
<BlueText style={styles.txId}>Txid</BlueText>
<BlueCopyToClipboardButton stringToCopy={this.state.tx.hash} />
</View>
<BlueText style={styles.txHash}>{this.state.tx.hash}</BlueText>
<TouchableOpacity
onPress={() => {
const url = `https://blockstream.info/tx/${this.state.tx.hash}`;
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
}
});
}}
>
<BlueText style={styles.txLink}>{loc.transactions.details_show_in_block_explorer}</BlueText>
</TouchableOpacity>
</>
)}
{'received' in this.state.tx && (
<>
<BlueText style={styles.rowCaption}>{loc.transactions.details_received}</BlueText>
<BlueText style={styles.rowValue}>{dayjs(this.state.tx.received).format('MM/DD/YYYY h:mm A')}</BlueText>
</>
)}
{'block_height' in this.state.tx && this.state.tx.block_height > 0 && (
<>
<BlueText style={styles.rowCaption}>{loc.transactions.details_block}</BlueText>
<BlueText style={styles.rowValue}>{this.state.tx.block_height}</BlueText>
</>
)}
{'inputs' in this.state.tx && (
<>
<BlueText style={styles.rowCaption}>{loc.transactions.details_inputs}</BlueText>
<BlueText style={styles.rowValue}>{this.state.tx.inputs.length}</BlueText>
</>
)}
{'outputs' in this.state.tx && this.state.tx.outputs.length > 0 && (
<>
<BlueText style={styles.rowCaption}>{loc.transactions.details_outputs}</BlueText>
<BlueText style={styles.rowValue}>{this.state.tx.outputs.length}</BlueText>
</>
)}
</BlueCard>
</ScrollView>
</SafeBlueArea>
);
}
}
TransactionsDetails.propTypes = {
route: PropTypes.shape({
name: PropTypes.string,
params: PropTypes.shape({
hash: PropTypes.string,
}),
}),
navigation: PropTypes.shape({
setParams: PropTypes.func,
}),
};
TransactionsDetails.navigationOptions = ({ navigation, route }) => ({
TransactionsDetails.navigationOptions = () => ({
...BlueNavigationStyle(),
title: loc.transactions.details_title,
headerStyle: {
...BlueNavigationStyle().headerStyle,
backgroundColor: BlueCurrentTheme.colors.customHeader,
},
headerRight: () => (
<TouchableOpacity disabled={route.params.isLoading === true} style={styles.save} onPress={route.params.handleOnSaveButtonTapped}>
<Text style={styles.saveText}>{loc.wallets.details_save}</Text>
</TouchableOpacity>
),
});

View file

@ -22,7 +22,7 @@ import {
BlueFormLabel,
BlueButton,
BlueNavigationStyle,
BlueButtonLinkHook,
BlueButtonLink,
BlueSpacing20,
} from '../../BlueComponents';
import { HDSegwitBech32Wallet, SegwitP2SHWallet, HDSegwitP2SHWallet, LightningCustodianWallet, AppStorage } from '../../class';
@ -310,7 +310,7 @@ const WalletsAdd = () => {
}
})()}
{isAdvancedOptionsEnabled && selectedWalletType === ButtonSelected.ONCHAIN && !isLoading && (
<BlueButtonLinkHook style={styles.import} title={entropyButtonText} onPress={navigateToEntropy} />
<BlueButtonLink style={styles.import} title={entropyButtonText} onPress={navigateToEntropy} />
)}
<BlueSpacing20 />
<View style={styles.createButton}>
@ -321,7 +321,7 @@ const WalletsAdd = () => {
)}
</View>
{!isLoading && (
<BlueButtonLinkHook
<BlueButtonLink
testID="ImportWallet"
style={styles.import}
title={loc.wallets.add_import_wallet}

View file

@ -15,9 +15,9 @@ import {
} from 'react-native';
import {
BlueButton,
BlueButtonLinkHook,
BlueButtonLink,
BlueFormMultiInput,
BlueLoadingHook,
BlueLoading,
BlueNavigationStyle,
BlueSpacing10,
BlueSpacing20,
@ -290,6 +290,13 @@ const WalletsAddMultisigStep2 = () => {
if (cosignersCopy.length === n) setIsOnCreateButtonEnabled(true);
setIsProvideMnemonicsModalVisible(false);
setIsLoading(false);
setImportText('');
};
const isValidMnemonicSeed = mnemonicSeed => {
const hd = new HDSegwitBech32Wallet();
hd.setSecret(mnemonicSeed);
return hd.validateMnemonic();
};
const onBarScanned = ret => {
@ -298,6 +305,9 @@ const WalletsAddMultisigStep2 = () => {
if (!ret.data) ret = { data: ret };
if (ret.data.toUpperCase().startsWith('UR')) {
alert('BC-UR not decoded. This should never happen');
} else if (isValidMnemonicSeed(ret.data)) {
setIsProvideMnemonicsModalVisible(true);
setImportText(ret.data);
} else {
let cosigner = new MultisigCosigner(ret.data);
if (!cosigner.isValid()) return alert(loc.multisig.invalid_cosigner);
@ -572,7 +582,7 @@ const WalletsAddMultisigStep2 = () => {
) : (
<BlueButton disabled={importText.trim().length === 0} title={loc.wallets.import_do_import} onPress={useMnemonicPhrase} />
)}
<BlueButtonLinkHook disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
<BlueButtonLink disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
</View>
</KeyboardAvoidingView>
</BottomModal>
@ -620,7 +630,7 @@ const WalletsAddMultisigStep2 = () => {
);
};
const footer = isLoading ? (
<BlueLoadingHook />
<BlueLoading />
) : (
<View style={styles.buttonBottom}>
<BlueButton title={loc.multisig.create} onPress={onCreate} disabled={!isOnCreateButtonEnabled} />

View file

@ -16,7 +16,7 @@ import {
StatusBar,
PermissionsAndroid,
} from 'react-native';
import { SecondButton, SafeBlueArea, BlueCard, BlueSpacing20, BlueNavigationStyle, BlueText, BlueLoadingHook } from '../../BlueComponents';
import { SecondButton, SafeBlueArea, BlueCard, BlueSpacing20, BlueNavigationStyle, BlueText, BlueLoading } from '../../BlueComponents';
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
import { HDLegacyBreadwalletWallet } from '../../class/wallets/hd-legacy-breadwallet-wallet';
import { HDLegacyP2PKHWallet } from '../../class/wallets/hd-legacy-p2pkh-wallet';
@ -368,7 +368,7 @@ const WalletDetails = () => {
return isLoading ? (
<View style={styles.root}>
<BlueLoadingHook />
<BlueLoading />
</View>
) : (
<SafeBlueArea style={styles.root}>

View file

@ -140,6 +140,15 @@ const WalletTransactions = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [walletID]);
// if balance of the wallet positive and there are no transactions, then
// it'a freshly impoted wallet and we need to refresh transactions
useEffect(() => {
if (dataSource.length === 0 && wallet.current.getBalance() > 0) {
refreshTransactions();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// if description of transaction has been changed we want to show new one
useFocusEffect(
useCallback(() => {

View file

@ -22,9 +22,9 @@ import ImagePicker from 'react-native-image-picker';
import {
BlueButton,
BlueButtonLinkHook,
BlueButtonLink,
BlueFormMultiInput,
BlueLoadingHook,
BlueLoading,
BlueNavigationStyle,
BlueSpacing10,
BlueSpacing20,
@ -441,7 +441,7 @@ const ViewEditMultisigCosigners = () => {
onPress={handleUseMnemonicPhrase}
/>
)}
<BlueButtonLinkHook disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
<BlueButtonLink disabled={isLoading} onPress={scanOrOpenFile} title={loc.wallets.import_scan_qr} />
</View>
</KeyboardAvoidingView>
</BottomModal>
@ -451,7 +451,7 @@ const ViewEditMultisigCosigners = () => {
if (isLoading)
return (
<SafeAreaView style={[styles.root, stylesHook.root]}>
<BlueLoadingHook />
<BlueLoading />
</SafeAreaView>
);

View file

@ -1,12 +1,4 @@
vim ios/BlueWallet/Info.plist
vim ios/BlueWalletWatch/Info.plist
vim "ios/BlueWalletWatch Extension/Info.plist"
vim "ios/TodayExtension/Info.plist"
vim ios/BlueWallet.xcodeproj/project.pbxproj
vim ios/WalletInformationWidget/Widgets/MarketWidget/Info.plist
vim ios/WalletInformationWidget/Widgets/WalletInformationAndMarketWidget/Info.plist
vim ios/WalletInformationWidget/Info.plist
vim ios/WalletInformationWidget/Widgets/PriceWidget/Info.plist
vim android/app/build.gradle
vim package.json
vim package-lock.json