Merge branch 'master' into clip

This commit is contained in:
Marcos Rodriguez Vélez 2024-10-27 15:04:55 -04:00 committed by GitHub
commit f8fd488158
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 1389 additions and 1045 deletions

View File

@ -1,14 +1,7 @@
import React, { useCallback, useMemo } from 'react';
import { Image, Keyboard, Platform, StyleSheet, Text, TextInput, View } from 'react-native';
import { scanQrHelper } from '../helpers/scan-qr';
import React from 'react';
import { Keyboard, StyleSheet, TextInput, View } from 'react-native';
import loc from '../loc';
import presentAlert from './Alert';
import ToolTipMenu from './TooltipMenu';
import { CommonToolTipActions } from '../typings/CommonToolTipActions';
import Clipboard from '@react-native-clipboard/clipboard';
import RNQRGenerator from 'rn-qr-generator';
import { showFilePickerAndReadFile, showImagePickerAndReadImage } from '../blue_modules/fs';
import { AddressInputScanButton } from './AddressInputScanButton';
import { useTheme } from './themes';
interface AddressInputProps {
@ -22,6 +15,8 @@ interface AddressInputProps {
editable?: boolean;
inputAccessoryViewID?: string;
onBlur?: () => void;
onFocus?: () => void;
testID?: string;
keyboardType?:
| 'default'
| 'numeric'
@ -41,6 +36,7 @@ interface AddressInputProps {
const AddressInput = ({
isLoading = false,
address = '',
testID = 'AddressInput',
placeholder = loc.send.details_address,
onChangeText,
onBarScanned,
@ -49,6 +45,7 @@ const AddressInput = ({
editable = true,
inputAccessoryViewID,
onBlur = () => {},
onFocus = () => {},
keyboardType = 'default',
}: AddressInputProps) => {
const { colors } = useTheme();
@ -58,11 +55,8 @@ const AddressInput = ({
borderBottomColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
},
scan: {
backgroundColor: colors.scanLabel,
},
scanText: {
color: colors.inverseForegroundColor,
input: {
color: colors.foregroundColor,
},
});
@ -71,129 +65,33 @@ const AddressInput = ({
Keyboard.dismiss();
};
const toolTipOnPress = useCallback(async () => {
await scanButtonTapped();
Keyboard.dismiss();
if (launchedBy) scanQrHelper(launchedBy, true).then(value => onBarScanned({ data: value }));
}, [launchedBy, onBarScanned, scanButtonTapped]);
const onMenuItemPressed = useCallback(
async (action: string) => {
if (onBarScanned === undefined) throw new Error('onBarScanned is required');
switch (action) {
case actionKeys.ScanQR:
scanButtonTapped();
if (launchedBy) {
scanQrHelper(launchedBy)
.then(value => onBarScanned({ data: value }))
.catch(error => {
presentAlert({ message: error.message });
});
}
break;
case CommonToolTipActions.PasteFromClipboard.id:
try {
let getImage: string | null = null;
if (Platform.OS === 'android') {
getImage = await Clipboard.getImage();
} else {
const hasImage = await Clipboard.hasImage();
if (hasImage) {
getImage = await Clipboard.getImageJPG();
}
}
if (getImage) {
try {
const base64Data = getImage.replace(/^data:image\/jpeg;base64,/, '');
const values = await RNQRGenerator.detect({
base64: base64Data,
});
if (values && values.values.length > 0) {
onChangeText(values.values[0]);
} else {
presentAlert({ message: loc.send.qr_error_no_qrcode });
}
} catch (error) {
presentAlert({ message: (error as Error).message });
}
} else {
const clipboardText = await Clipboard.getString();
onChangeText(clipboardText);
}
} catch (error) {
presentAlert({ message: (error as Error).message });
}
break;
case actionKeys.ChoosePhoto:
showImagePickerAndReadImage()
.then(value => {
if (value) {
onChangeText(value);
}
})
.catch(error => {
presentAlert({ message: error.message });
});
break;
case actionKeys.ImportFile:
showFilePickerAndReadFile()
.then(value => {
if (value.data) {
onChangeText(value.data);
}
})
.catch(error => {
presentAlert({ message: error.message });
});
break;
}
Keyboard.dismiss();
},
[launchedBy, onBarScanned, onChangeText, scanButtonTapped],
);
const buttonStyle = useMemo(() => [styles.scan, stylesHook.scan], [stylesHook.scan]);
return (
<View style={[styles.root, stylesHook.root]}>
<TextInput
testID="AddressInput"
testID={testID}
onChangeText={onChangeText}
placeholder={placeholder}
placeholderTextColor="#81868e"
value={address}
style={styles.input}
style={[styles.input, stylesHook.input]}
editable={!isLoading && editable}
multiline={!editable}
inputAccessoryViewID={inputAccessoryViewID}
clearButtonMode="while-editing"
onBlur={onBlurEditing}
onFocus={onFocus}
autoCapitalize="none"
autoCorrect={false}
keyboardType={keyboardType}
/>
{editable ? (
<ToolTipMenu
actions={actions}
isButton
onPressMenuItem={onMenuItemPressed}
testID="BlueAddressInputScanQrButton"
disabled={isLoading}
onPress={toolTipOnPress}
buttonStyle={buttonStyle}
accessibilityLabel={loc.send.details_scan}
accessibilityHint={loc.send.details_scan_hint}
>
<Image source={require('../img/scan-white.png')} accessible={false} />
<Text style={[styles.scanText, stylesHook.scanText]} accessible={false}>
{loc.send.details_scan}
</Text>
</ToolTipMenu>
<AddressInputScanButton
isLoading={isLoading}
launchedBy={launchedBy}
scanButtonTapped={scanButtonTapped}
onBarScanned={onBarScanned}
onChangeText={onChangeText}
/>
) : null}
</View>
);
@ -206,7 +104,6 @@ const styles = StyleSheet.create({
borderBottomWidth: 0.5,
minHeight: 44,
height: 44,
marginHorizontal: 20,
alignItems: 'center',
marginVertical: 8,
borderRadius: 4,
@ -215,66 +112,7 @@ const styles = StyleSheet.create({
flex: 1,
marginHorizontal: 8,
minHeight: 33,
color: '#81868e',
},
scan: {
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
},
scanText: {
marginLeft: 4,
},
});
const actionKeys = {
ScanQR: 'scan_qr',
PasteFromClipboard: 'copy_from_clipboard',
ChoosePhoto: 'choose_photo',
ImportFile: 'import_file',
};
const actionIcons = {
ScanQR: {
iconValue: Platform.OS === 'ios' ? 'qrcode' : 'ic_menu_camera',
},
ImportFile: {
iconValue: 'doc',
},
ChoosePhoto: {
iconValue: Platform.OS === 'ios' ? 'photo' : 'ic_menu_gallery',
},
Clipboard: {
iconValue: Platform.OS === 'ios' ? 'doc' : 'ic_menu_file',
},
};
const actions = [
{
id: actionKeys.ScanQR,
text: loc.wallets.list_long_scan,
icon: actionIcons.ScanQR,
},
{
id: actionKeys.PasteFromClipboard,
text: loc.wallets.paste_from_clipboard,
icon: actionIcons.Clipboard,
},
{
id: actionKeys.ChoosePhoto,
text: loc.wallets.list_long_choose,
icon: actionIcons.ChoosePhoto,
},
{
id: actionKeys.ImportFile,
text: loc.wallets.import_file,
icon: actionIcons.ImportFile,
},
];
export default AddressInput;

View File

@ -0,0 +1,176 @@
import React, { useCallback, useMemo } from 'react';
import { Image, Keyboard, Platform, StyleSheet, Text } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard';
import ToolTipMenu from './TooltipMenu';
import loc from '../loc';
import { scanQrHelper } from '../helpers/scan-qr';
import { showFilePickerAndReadFile, showImagePickerAndReadImage } from '../blue_modules/fs';
import presentAlert from './Alert';
import { useTheme } from './themes';
import RNQRGenerator from 'rn-qr-generator';
import { CommonToolTipActions } from '../typings/CommonToolTipActions';
import { useSettings } from '../hooks/context/useSettings';
interface AddressInputScanButtonProps {
isLoading: boolean;
launchedBy?: string;
scanButtonTapped: () => void;
onBarScanned: (ret: { data?: any }) => void;
onChangeText: (text: string) => void;
}
export const AddressInputScanButton = ({
isLoading,
launchedBy,
scanButtonTapped,
onBarScanned,
onChangeText,
}: AddressInputScanButtonProps) => {
const { colors } = useTheme();
const { isClipboardGetContentEnabled } = useSettings();
const stylesHook = StyleSheet.create({
scan: {
backgroundColor: colors.scanLabel,
},
scanText: {
color: colors.inverseForegroundColor,
},
});
const toolTipOnPress = useCallback(async () => {
await scanButtonTapped();
Keyboard.dismiss();
if (launchedBy) scanQrHelper(launchedBy, true).then(value => onBarScanned({ data: value }));
}, [launchedBy, onBarScanned, scanButtonTapped]);
const actions = useMemo(() => {
const availableActions = [
CommonToolTipActions.ScanQR,
CommonToolTipActions.ChoosePhoto,
CommonToolTipActions.ImportFile,
{
...CommonToolTipActions.PasteFromClipboard,
hidden: !isClipboardGetContentEnabled,
},
];
return availableActions;
}, [isClipboardGetContentEnabled]);
const onMenuItemPressed = useCallback(
async (action: string) => {
if (onBarScanned === undefined) throw new Error('onBarScanned is required');
switch (action) {
case CommonToolTipActions.ScanQR.id:
scanButtonTapped();
if (launchedBy) {
scanQrHelper(launchedBy)
.then(value => onBarScanned({ data: value }))
.catch(error => {
presentAlert({ message: error.message });
});
}
break;
case CommonToolTipActions.PasteFromClipboard.id:
try {
let getImage: string | null = null;
if (Platform.OS === 'android') {
getImage = await Clipboard.getImage();
} else {
const hasImage = await Clipboard.hasImage();
if (hasImage) {
getImage = await Clipboard.getImageJPG();
}
}
if (getImage) {
try {
const base64Data = getImage.replace(/^data:image\/jpeg;base64,/, '');
const values = await RNQRGenerator.detect({
base64: base64Data,
});
if (values && values.values.length > 0) {
onChangeText(values.values[0]);
} else {
presentAlert({ message: loc.send.qr_error_no_qrcode });
}
} catch (error) {
presentAlert({ message: (error as Error).message });
}
} else {
const clipboardText = await Clipboard.getString();
onChangeText(clipboardText);
}
} catch (error) {
presentAlert({ message: (error as Error).message });
}
break;
case CommonToolTipActions.ChoosePhoto.id:
showImagePickerAndReadImage()
.then(value => {
if (value) {
onChangeText(value);
}
})
.catch(error => {
presentAlert({ message: error.message });
});
break;
case CommonToolTipActions.ImportFile.id:
showFilePickerAndReadFile()
.then(value => {
if (value.data) {
onChangeText(value.data);
}
})
.catch(error => {
presentAlert({ message: error.message });
});
break;
}
Keyboard.dismiss();
},
[launchedBy, onBarScanned, onChangeText, scanButtonTapped],
);
const buttonStyle = useMemo(() => [styles.scan, stylesHook.scan], [stylesHook.scan]);
return (
<ToolTipMenu
actions={actions}
isButton
onPressMenuItem={onMenuItemPressed}
testID="BlueAddressInputScanQrButton"
disabled={isLoading}
onPress={toolTipOnPress}
buttonStyle={buttonStyle}
accessibilityLabel={loc.send.details_scan}
accessibilityHint={loc.send.details_scan_hint}
>
<Image source={require('../img/scan-white.png')} accessible={false} />
<Text style={[styles.scanText, stylesHook.scanText]} accessible={false}>
{loc.send.details_scan}
</Text>
</ToolTipMenu>
);
};
const styles = StyleSheet.create({
scan: {
height: 36,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderRadius: 4,
paddingVertical: 4,
paddingHorizontal: 8,
marginHorizontal: 4,
},
scanText: {
marginLeft: 4,
},
});

View File

@ -1,5 +1,5 @@
import React, { forwardRef } from 'react';
import { StyleProp, StyleSheet, Text, TouchableOpacity, TouchableOpacityProps, View, ViewStyle } from 'react-native';
import { ActivityIndicator, StyleProp, StyleSheet, Text, TouchableOpacity, TouchableOpacityProps, View, ViewStyle } from 'react-native';
import { Icon } from '@rneui/themed';
import { useTheme } from './themes';
@ -17,6 +17,7 @@ interface ButtonProps extends TouchableOpacityProps {
title?: string;
style?: StyleProp<ViewStyle>;
onPress?: () => void;
showActivityIndicator?: boolean;
}
export const Button = forwardRef<React.ElementRef<typeof TouchableOpacity>, ButtonProps>((props, ref) => {
@ -40,7 +41,9 @@ export const Button = forwardRef<React.ElementRef<typeof TouchableOpacity>, Butt
color: fontColor,
};
const buttonView = (
const buttonView = props.showActivityIndicator ? (
<ActivityIndicator size="small" color={textStyle.color} />
) : (
<>
{props.icon && <Icon name={props.icon.name} type={props.icon.type} color={props.icon.color} />}
{props.title && <Text style={textStyle}>{props.title}</Text>}

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React, { useMemo, useCallback } from 'react';
import { TouchableOpacity, Text, StyleSheet, LayoutAnimation, View } from 'react-native';
import { useStorage } from '../hooks/context/useStorage';
import loc, { formatBalanceWithoutSuffix } from '../loc';
@ -18,99 +18,94 @@ const TotalWalletsBalance: React.FC = () => {
useSettings();
const { colors } = useTheme();
const styleHooks = StyleSheet.create({
balance: {
color: colors.foregroundColor,
},
currency: {
color: colors.foregroundColor,
},
});
const styleHooks = useMemo(
() => ({
balance: { color: colors.foregroundColor },
currency: { color: colors.foregroundColor },
}),
[colors.foregroundColor],
);
// Calculate total balance from all wallets
const totalBalance = wallets.reduce((prev, curr) => {
if (!curr.hideBalance) {
return prev + curr.getBalance();
}
return prev;
}, 0);
const totalBalance = useMemo(() => wallets.reduce((prev, curr) => (!curr.hideBalance ? prev + curr.getBalance() : prev), 0), [wallets]);
const formattedBalance = useMemo(
() => formatBalanceWithoutSuffix(Number(totalBalance), totalBalancePreferredUnit, true),
() => formatBalanceWithoutSuffix(totalBalance, totalBalancePreferredUnit, true),
// eslint-disable-next-line react-hooks/exhaustive-deps
[totalBalance, totalBalancePreferredUnit, preferredFiatCurrency],
);
const toolTipActions = useMemo(() => {
let viewIn;
const viewInFiat = {
...CommonToolTipActions.ViewInFiat,
text: loc.formatString(loc.total_balance_view.view_in_fiat, { currency: preferredFiatCurrency.endPointKey }),
hidden: totalBalancePreferredUnit === BitcoinUnit.LOCAL_CURRENCY,
};
if (totalBalancePreferredUnit === BitcoinUnit.SATS) {
viewIn = {
...CommonToolTipActions.ViewInFiat,
text: loc.formatString(loc.total_balance_view.view_in_fiat, { currency: preferredFiatCurrency.endPointKey }),
};
} else if (totalBalancePreferredUnit === BitcoinUnit.LOCAL_CURRENCY) {
viewIn = CommonToolTipActions.ViewInBitcoin;
} else if (totalBalancePreferredUnit === BitcoinUnit.BTC) {
viewIn = CommonToolTipActions.ViewInSats;
} else {
viewIn = CommonToolTipActions.ViewInBitcoin;
}
const viewInSats = {
...CommonToolTipActions.ViewInSats,
hidden: totalBalancePreferredUnit === BitcoinUnit.SATS,
};
return [viewIn, CommonToolTipActions.CopyAmount, CommonToolTipActions.HideBalance];
const viewInBitcoin = {
...CommonToolTipActions.ViewInBitcoin,
hidden: totalBalancePreferredUnit === BitcoinUnit.BTC,
};
const viewInActions = {
id: 'viewInActions',
text: '',
subactions: [viewInFiat, viewInSats, viewInBitcoin],
displayInline: true,
};
return [viewInActions, CommonToolTipActions.CopyAmount, CommonToolTipActions.HideBalance];
}, [preferredFiatCurrency.endPointKey, totalBalancePreferredUnit]);
const onPressMenuItem = useMemo(
() => async (id: string) => {
const onPressMenuItem = useCallback(
async (id: string) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
switch (id) {
case CommonToolTipActions.ViewInFiat.id:
case CommonToolTipActions.ViewInBitcoin.id:
case CommonToolTipActions.ViewInSats.id:
switch (totalBalancePreferredUnit) {
case BitcoinUnit.BTC:
await setTotalBalancePreferredUnitStorage(BitcoinUnit.SATS);
break;
case BitcoinUnit.SATS:
await setTotalBalancePreferredUnitStorage(BitcoinUnit.LOCAL_CURRENCY);
break;
case BitcoinUnit.LOCAL_CURRENCY:
await setTotalBalancePreferredUnitStorage(BitcoinUnit.BTC);
break;
default:
break;
}
break;
case CommonToolTipActions.HideBalance.id:
setIsTotalBalanceEnabledStorage(false);
break;
case CommonToolTipActions.CopyAmount.id:
Clipboard.setString(formattedBalance.toString());
break;
default:
break;
if (id === CommonToolTipActions.ViewInFiat.id) {
await setTotalBalancePreferredUnitStorage(BitcoinUnit.LOCAL_CURRENCY);
} else if (id === CommonToolTipActions.ViewInSats.id) {
await setTotalBalancePreferredUnitStorage(BitcoinUnit.SATS);
} else if (id === CommonToolTipActions.ViewInBitcoin.id) {
await setTotalBalancePreferredUnitStorage(BitcoinUnit.BTC);
} else if (id === CommonToolTipActions.HideBalance.id) {
setIsTotalBalanceEnabledStorage(false);
} else if (id === CommonToolTipActions.CopyAmount.id) {
Clipboard.setString(formattedBalance.toString());
}
},
[totalBalancePreferredUnit, setIsTotalBalanceEnabledStorage, formattedBalance, setTotalBalancePreferredUnitStorage],
[setIsTotalBalanceEnabledStorage, formattedBalance, setTotalBalancePreferredUnitStorage],
);
const handleBalanceOnPress = useCallback(async () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
const nextUnit =
totalBalancePreferredUnit === BitcoinUnit.BTC
? BitcoinUnit.SATS
: totalBalancePreferredUnit === BitcoinUnit.SATS
? BitcoinUnit.LOCAL_CURRENCY
: BitcoinUnit.BTC;
await setTotalBalancePreferredUnitStorage(nextUnit);
}, [totalBalancePreferredUnit, setTotalBalancePreferredUnitStorage]);
if (wallets.length <= 1) return null;
return (
(wallets.length > 1 && (
<ToolTipMenu actions={toolTipActions} onPressMenuItem={onPressMenuItem}>
<View style={styles.container}>
<Text style={styles.label}>{loc.wallets.total_balance}</Text>
<TouchableOpacity onPress={() => onPressMenuItem(CommonToolTipActions.ViewInBitcoin.id)}>
<Text style={[styles.balance, styleHooks.balance]}>
{formattedBalance}{' '}
{totalBalancePreferredUnit !== BitcoinUnit.LOCAL_CURRENCY && (
<Text style={[styles.currency, styleHooks.currency]}>{totalBalancePreferredUnit}</Text>
)}
</Text>
</TouchableOpacity>
</View>
</ToolTipMenu>
)) ||
null
<ToolTipMenu actions={toolTipActions} onPressMenuItem={onPressMenuItem}>
<View style={styles.container}>
<Text style={styles.label}>{loc.wallets.total_balance}</Text>
<TouchableOpacity onPress={handleBalanceOnPress}>
<Text style={[styles.balance, styleHooks.balance]}>
{formattedBalance}{' '}
{totalBalancePreferredUnit !== BitcoinUnit.LOCAL_CURRENCY && (
<Text style={[styles.currency, styleHooks.currency]}>{totalBalancePreferredUnit}</Text>
)}
</Text>
</TouchableOpacity>
</View>
</ToolTipMenu>
);
};

View File

@ -324,6 +324,8 @@ lane :build_app_lane do
workspace_path = File.join(project_root, "ios", "BlueWallet.xcworkspace")
export_options_path = File.join(project_root, "ios", "export_options.plist")
clear_derived_data_lane
begin
build_ios_app(
scheme: "BlueWallet",

View File

@ -120,6 +120,11 @@
B45010A62C1507DE00619044 /* CustomSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B45010A52C1507DE00619044 /* CustomSegmentedControlManager.m */; };
B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */ = {isa = PBXBuildFile; fileRef = B4549F352B82B10D002E3153 /* ci_post_clone.sh */; };
B461B852299599F800E431AA /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = B461B851299599F800E431AA /* AppDelegate.mm */; };
B4742E972CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; };
B4742E982CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; };
B4742E992CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; };
B4742E9A2CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; };
B4742E9B2CCDBE8300380EEE /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B4742E962CCDBE8300380EEE /* Localizable.xcstrings */; };
B48A6A292C1DF01000030AB9 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B48A6A282C1DF01000030AB9 /* KeychainSwift */; };
B4AB225D2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; };
B4AB225E2B02AD12001F4328 /* XMLParserDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */; };
@ -131,7 +136,7 @@
B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */; };
B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; };
B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EFF73A2C3F6C5E0095D655 /* MockData.swift */; };
C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -347,6 +352,8 @@
B4549F352B82B10D002E3153 /* ci_post_clone.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = "<group>"; };
B461B850299599F800E431AA /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = BlueWallet/AppDelegate.h; sourceTree = "<group>"; };
B461B851299599F800E431AA /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = BlueWallet/AppDelegate.mm; sourceTree = "<group>"; };
B4742E962CCDBE8300380EEE /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
B4742E9C2CCDC31300380EEE /* en_US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en_US; path = en_US.lproj/Interface.strings; sourceTree = "<group>"; };
B47B21EB2B2128B8001F6690 /* BlueWalletUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueWalletUITests.swift; sourceTree = "<group>"; };
B49038D82B8FBAD300A8164A /* BlueWalletUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlueWalletUITest.swift; sourceTree = "<group>"; };
B4AB225C2B02AD12001F4328 /* XMLParserDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLParserDelegate.swift; sourceTree = "<group>"; };
@ -381,7 +388,7 @@
files = (
782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */,
764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */,
C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */,
C978A716948AB7DEC5B6F677 /* (null) in Frameworks */,
17CDA0718F42DB2CE856C872 /* libPods-BlueWallet.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -458,6 +465,7 @@
6D32C5C42596CE2F008C077C /* EventEmitter.h */,
6D32C5C52596CE3A008C077C /* EventEmitter.m */,
84E05A832721191B001A0D3A /* Settings.bundle */,
B4742E962CCDBE8300380EEE /* Localizable.xcstrings */,
);
name = BlueWallet;
sourceTree = "<group>";
@ -928,10 +936,9 @@
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "BlueWallet" */;
compatibilityVersion = "Xcode 15.0";
developmentRegion = English;
developmentRegion = en_US;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
af,
@ -957,6 +964,8 @@
tr,
xh,
nb,
en_US,
"en-US",
);
mainGroup = 83CBB9F61A601CBA00E9B192;
packageReferences = (
@ -985,6 +994,7 @@
6DF25A9F249DB97E001D06F5 /* LaunchScreen.storyboard in Resources */,
B440340F2BCC40A400162242 /* fiatUnits.json in Resources */,
84E05A842721191B001A0D3A /* Settings.bundle in Resources */,
B4742E972CCDBE8300380EEE /* Localizable.xcstrings in Resources */,
B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */,
B41C2E562BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
@ -995,6 +1005,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B4742E9A2CCDBE8300380EEE /* Localizable.xcstrings in Resources */,
6D2A6464258BA92D0092292B /* Stickers.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1005,6 +1016,7 @@
files = (
B41C2E582BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */,
B44034112BCC40A400162242 /* fiatUnits.json in Resources */,
B4742E9B2CCDBE8300380EEE /* Localizable.xcstrings in Resources */,
6DD410B7266CAF5C0087DE03 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1013,6 +1025,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B4742E982CCDBE8300380EEE /* Localizable.xcstrings in Resources */,
B40D4E36225841ED00428FCC /* Assets.xcassets in Resources */,
B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */,
);
@ -1024,6 +1037,7 @@
files = (
B41C2E572BB3DCB8000FE097 /* PrivacyInfo.xcprivacy in Resources */,
B44034102BCC40A400162242 /* fiatUnits.json in Resources */,
B4742E992CCDBE8300380EEE /* Localizable.xcstrings in Resources */,
B4EE583C226703320003363C /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1336,6 +1350,7 @@
6D294A9B24D512770039E22B /* tr */,
6D294A9D24D5127F0039E22B /* xh */,
B4B31A352C77BBA000663334 /* nb */,
B4742E9C2CCDC31300380EEE /* en_US */,
);
name = Interface.storyboard;
sourceTree = "<group>";
@ -1713,6 +1728,7 @@
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
USE_HERMES = true;
WATCHOS_DEPLOYMENT_TARGET = 7.0;
@ -1777,6 +1793,7 @@
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
USE_HERMES = true;
VALIDATE_PRODUCT = YES;

View File

@ -56,6 +56,7 @@
center.delegate = self;
[self setupUserDefaultsListener];
[self registerNotificationCategories];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@ -72,7 +73,25 @@
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
}
- (void)registerNotificationCategories {
// Define two actions: "View Address in Browser" and "View Transaction in Browser"
UNNotificationAction *viewAddressTransactionsAction = [UNNotificationAction actionWithIdentifier:@"VIEW_ADDRESS_TRANSACTIONS"
title:NSLocalizedString(@"VIEW_ADDRESS_TRANSACTIONS_TITLE", nil)
options:UNNotificationActionOptionForeground];
UNNotificationAction *viewTransactionDetailsAction = [UNNotificationAction actionWithIdentifier:@"VIEW_TRANSACTION_DETAILS"
title:NSLocalizedString(@"VIEW_TRANSACTION_DETAILS_TITLE", nil)
options:UNNotificationActionOptionForeground];
UNNotificationCategory *transactionCategory = [UNNotificationCategory categoryWithIdentifier:@"TRANSACTION_CATEGORY"
actions:@[viewAddressTransactionsAction, viewTransactionDetailsAction]
intentIdentifiers:@[]
options:UNNotificationCategoryOptionCustomDismissAction];
[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:transactionCategory]];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
@ -259,12 +278,37 @@
{
[RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
}
// Required for localNotification event
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler
{
[RNCPushNotificationIOS didReceiveNotificationResponse:response];
NSDictionary *userInfo = response.notification.request.content.userInfo;
NSString *blockExplorer = [[[NSUserDefaults standardUserDefaults] initWithSuiteName:@"group.io.bluewallet.bluewallet"] stringForKey:@"blockExplorer"];
if (blockExplorer == nil || [blockExplorer length] == 0) {
blockExplorer = @"https://www.mempool.space";
}
NSString *address = userInfo[@"data"][@"address"];
NSString *txid = userInfo[@"data"][@"txid"];
if ([response.actionIdentifier isEqualToString:@"VIEW_ADDRESS_TRANSACTIONS"] && address) {
NSString *urlString = [NSString stringWithFormat:@"%@/address/%@", blockExplorer, address];
NSURL *url = [NSURL URLWithString:urlString];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
}
}
else if ([response.actionIdentifier isEqualToString:@"VIEW_TRANSACTION_DETAILS"] && txid) {
NSString *urlString = [NSString stringWithFormat:@"%@/tx/%@", blockExplorer, txid];
NSURL *url = [NSURL URLWithString:urlString];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
}
}
[RNCPushNotificationIOS didReceiveNotificationResponse:response];
completionHandler();
}
// Clear cache on app launch

View File

@ -0,0 +1,105 @@
/* Class = "WKInterfaceButton"; title = "Amount"; ObjectID = "0Hm-hv-Yi3"; */
"0Hm-hv-Yi3.title" = "Amount";
/* Class = "WKInterfaceButton"; title = "8"; ObjectID = "3FQ-tZ-9kd"; */
"3FQ-tZ-9kd.title" = "8";
/* Class = "WKInterfaceButton"; title = "Create"; ObjectID = "6eh-lx-UEe"; */
"6eh-lx-UEe.title" = "Create";
/* Class = "WKInterfaceButton"; title = "Create Invoice"; ObjectID = "7bc-tt-Pab"; */
"7bc-tt-Pab.title" = "Create Invoice";
/* Class = "WKInterfaceButton"; title = "5"; ObjectID = "AA6-Gq-qRe"; */
"AA6-Gq-qRe.title" = "5";
/* Class = "WKInterfaceLabel"; text = "memo"; ObjectID = "AJ8-p9-ID7"; */
"AJ8-p9-ID7.text" = "memo";
/* Class = "WKInterfaceController"; title = "BlueWallet"; ObjectID = "AgC-eL-Hgc"; */
"AgC-eL-Hgc.title" = "BlueWallet";
/* Class = "WKInterfaceLabel"; text = "Time"; ObjectID = "GqE-KB-TRD"; */
"GqE-KB-TRD.text" = "Time";
/* Class = "WKInterfaceLabel"; text = "No wallets available. Please, add one by opening BlueWallet on your iPhone."; ObjectID = "I2I-8t-hp3"; */
"I2I-8t-hp3.text" = "No wallets available. Please, add one by opening BlueWallet on your iPhone.";
/* Class = "WKInterfaceLabel"; text = "Alert Label"; ObjectID = "IdU-wH-bcW"; */
"IdU-wH-bcW.text" = "Alert Label";
/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "JMO-XZ-1si"; */
"JMO-XZ-1si.text" = "Label";
/* Class = "WKInterfaceButton"; title = "9"; ObjectID = "NJM-uR-nyO"; */
"NJM-uR-nyO.title" = "9";
/* Class = "WKInterfaceButton"; title = "6"; ObjectID = "Nt9-we-M9f"; */
"Nt9-we-M9f.title" = "6";
/* Class = "WKInterfaceLabel"; text = "Wallet"; ObjectID = "PQi-JV-aYW"; */
"PQi-JV-aYW.text" = "Wallet";
/* Class = "WKInterfaceLabel"; text = "Balance"; ObjectID = "QYx-3e-6zf"; */
"QYx-3e-6zf.text" = "Balance";
/* Class = "WKInterfaceMenuItem"; title = "Customize"; ObjectID = "RHB-IJ-Utd"; */
"RHB-IJ-Utd.title" = "Customize";
/* Class = "WKInterfaceButton"; title = "0"; ObjectID = "S1H-Id-l6g"; */
"S1H-Id-l6g.title" = "0";
/* Class = "WKInterfaceButton"; title = "3"; ObjectID = "TKO-lc-aYf"; */
"TKO-lc-aYf.title" = "3";
/* Class = "WKInterfaceLabel"; text = "Balance"; ObjectID = "WTr-jJ-w7L"; */
"WTr-jJ-w7L.text" = "Balance";
/* Class = "WKInterfaceController"; title = "Transactions"; ObjectID = "XWa-4i-Abg"; */
"XWa-4i-Abg.title" = "Transactions";
/* Class = "WKInterfaceButton"; title = "2"; ObjectID = "aUI-EE-NVw"; */
"aUI-EE-NVw.title" = "2";
/* Class = "WKInterfaceButton"; title = "Receive"; ObjectID = "bPO-h8-ccD"; */
"bPO-h8-ccD.title" = "Receive";
/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "c3W-8T-srG"; */
"c3W-8T-srG.text" = "Label";
/* Class = "WKInterfaceController"; title = "Receive"; ObjectID = "egq-Yw-qK5"; */
"egq-Yw-qK5.title" = "Receive";
/* Class = "WKInterfaceButton"; title = "Description"; ObjectID = "fcI-6Z-moQ"; */
"fcI-6Z-moQ.title" = "Description";
/* Class = "WKInterfaceButton"; title = "."; ObjectID = "g6Z-9t-ahQ"; */
"g6Z-9t-ahQ.title" = ".";
/* Class = "WKInterfaceButton"; title = "1"; ObjectID = "ghD-Jq-ubw"; */
"ghD-Jq-ubw.title" = "1";
/* Class = "WKInterfaceButton"; title = "View XPUB"; ObjectID = "j0O-fq-mwp"; */
"j0O-fq-mwp.title" = "View XPUB";
/* Class = "WKInterfaceButton"; title = "4"; ObjectID = "kH2-N1-Hbe"; */
"kH2-N1-Hbe.title" = "4";
/* Class = "WKInterfaceLabel"; text = "Creating Invoice..."; ObjectID = "n5f-iL-ib7"; */
"n5f-iL-ib7.text" = "Creating Invoice...";
/* Class = "WKInterfaceButton"; title = "7"; ObjectID = "ohU-B0-mvg"; */
"ohU-B0-mvg.title" = "7";
/* Class = "WKInterfaceLabel"; text = "No Transactions"; ObjectID = "pi4-Bk-Jiq"; */
"pi4-Bk-Jiq.text" = "No Transactions";
/* Class = "WKInterfaceButton"; title = "<"; ObjectID = "q8Q-tK-nzd"; */
"q8Q-tK-nzd.title" = "<";
/* Class = "WKInterfaceLabel"; text = "Wallet"; ObjectID = "qpj-I1-cWt"; */
"qpj-I1-cWt.text" = "Wallet";
/* Class = "WKInterfaceLabel"; text = "Amount"; ObjectID = "sAS-LI-RY7"; */
"sAS-LI-RY7.text" = "Amount";

62
ios/Localizable.xcstrings Normal file
View File

@ -0,0 +1,62 @@
{
"sourceLanguage" : "en_US",
"strings" : {
"VIEW_ADDRESS_TRANSACTIONS_TITLE" : {
"extractionState" : "manual",
"localizations" : {
"en_US" : {
"stringUnit" : {
"state" : "translated",
"value" : "View Address in Browser"
}
},
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Ver dirección en el navegador"
}
}
}
},
"VIEW_IN_BROWSER_TITLE" : {
"localizations" : {
"en_US" : {
"stringUnit" : {
"state" : "translated",
"value" : "View in Browser"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ver en el navegador"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Voir dans le navigateur"
}
}
}
},
"VIEW_TRANSACTION_DETAILS_TITLE" : {
"extractionState" : "manual",
"localizations" : {
"en_US" : {
"stringUnit" : {
"state" : "translated",
"value" : "View Transaction in Browser"
}
},
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Ver transacción en el navegador"
}
}
}
}
},
"version" : "1.0"
}

View File

@ -1,6 +1,6 @@
PODS:
- boost (1.84.0)
- BugsnagReactNative (8.1.1):
- BugsnagReactNative (8.1.2):
- React-Core
- BVLinearGradient (2.8.3):
- React-Core
@ -12,12 +12,12 @@ PODS:
- hermes-engine (0.75.4):
- hermes-engine/Pre-built (= 0.75.4)
- hermes-engine/Pre-built (0.75.4)
- lottie-ios (4.4.1)
- lottie-react-native (6.7.2):
- lottie-ios (4.5.0)
- lottie-react-native (7.0.0):
- DoubleConversion
- glog
- hermes-engine
- lottie-ios (= 4.4.1)
- lottie-ios (= 4.5.0)
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
@ -1592,7 +1592,7 @@ PODS:
- React-Core
- RealmJS (20.0.0):
- React
- RNCAsyncStorage (1.24.0):
- RNCAsyncStorage (2.0.0):
- React-Core
- RNCClipboard (1.14.2):
- React-Core
@ -1600,7 +1600,7 @@ PODS:
- React-Core
- RNDefaultPreference (1.4.4):
- React-Core
- RNDeviceInfo (11.1.0):
- RNDeviceInfo (13.2.0):
- React-Core
- RNFS (2.20.0):
- React-Core
@ -1627,11 +1627,30 @@ PODS:
- Yoga
- RNHandoff (0.0.3):
- React
- RNKeychain (8.2.0):
- RNKeychain (9.1.0):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNLocalize (3.2.1):
- React-Core
- RNPermissions (4.1.5):
- RNPermissions (5.0.2):
- React-Core
- RNQrGenerator (1.4.2):
- React
@ -1770,8 +1789,27 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNShare (10.2.1):
- RNShare (11.0.4):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2024.01.01.00)
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNSVG (15.8.0):
- React-Core
- RNVectorIcons (10.2.0):
@ -2143,7 +2181,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost: 4cb898d0bf20404aab1850c656dcea009429d6c1
BugsnagReactNative: c8b6afecdf4dc127246de7ebef082bc71d96ac51
BugsnagReactNative: d1d736effdbbf529126bc39a3a9ca23e305426dd
BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5
@ -2151,8 +2189,8 @@ SPEC CHECKSUMS:
fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120
glog: 69ef571f3de08433d766d614c73a9838a06bf7eb
hermes-engine: ea92f60f37dba025e293cbe4b4a548fd26b610a0
lottie-ios: e047b1d2e6239b787cc5e9755b988869cf190494
lottie-react-native: 31197e5c65aa7cb59e6affcefaf901588bb708c4
lottie-ios: a881093fab623c467d3bce374367755c272bdd59
lottie-react-native: 2a3335e7f3cfdc881f400b08c7e9e84d18920db1
RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740
RCTDeprecation: 726d24248aeab6d7180dac71a936bbca6a994ed1
RCTRequired: a94e7febda6db0345d207e854323c37e3a31d93b
@ -2222,24 +2260,24 @@ SPEC CHECKSUMS:
ReactCommon: 6a952e50c2a4b694731d7682aaa6c79bc156e4ad
ReactNativeCameraKit: 9d46a5d7dd544ca64aa9c03c150d2348faf437eb
RealmJS: 6946b520bada5568b7e92a5242e138d3a19aa69f
RNCAsyncStorage: ec53e44dc3e75b44aa2a9f37618a49c3bc080a7a
RNCAsyncStorage: d35c79ffba52c1013013e16b1fc295aec2feabb6
RNCClipboard: 5e503962f0719ace8f7fdfe9c60282b526305c85
RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
RNDeviceInfo: b899ce37a403a4dea52b7cb85e16e49c04a5b88e
RNDeviceInfo: 29e01d5ae94bdb5a0f6c11a4c438132545b4df80
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: 6dfe7692a191ee224748964127114edf057a1475
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
RNKeychain: bfe3d12bf4620fe488771c414530bf16e88f3678
RNKeychain: 958a200b26c2df5036222105550290ac0ed98c90
RNLocalize: 4f22418187ecd5ca693231093ff1d912d1b3c9bc
RNPermissions: 9fa74223844f437bc309e112994859dc47194829
RNPermissions: 81b5a3e2441f0be92f807519c0a4c4f693b5e57c
RNQrGenerator: 5c12ab86443a07e923735800679da7b6fcaaeb31
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNRate: ef3bcff84f39bb1d1e41c5593d3eea4aab2bd73a
RNReactNativeHapticFeedback: 0d591ea1e150f36cb96d868d4e8d77272243d78a
RNReanimated: f6a10979b3701f8029c71dbfe35d0ff4328dce4c
RNScreens: 19719a9c326e925498ac3b2d35c4e50fe87afc06
RNShare: 0fad69ae2d71de9d1f7b9a43acf876886a6cb99c
RNShare: eaeb5e7dc1618d19db6234da1af91fc60dd6bc0f
RNSVG: 8b1a777d54096b8c2a0fd38fc9d5a454332bbb4d
RNVectorIcons: 6382277afab3c54658e9d555ee0faa7a37827136
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236

View File

@ -22,8 +22,8 @@
"close": "Close",
"change_input_currency": "Change input currency",
"refresh": "Refresh",
"pick_image": "Choose image from library",
"pick_file": "Choose a file",
"pick_image": "Choose from library",
"pick_file": "Choose file",
"enter_amount": "Enter amount",
"qr_custom_input_button": "Tap 10 times to enter custom input",
"unlock": "Unlock",
@ -247,18 +247,17 @@
"set_electrum_server_as_default": "Set {server} as the default Electrum server?",
"set_lndhub_as_default": "Set {url} as the default LNDhub server?",
"electrum_settings_server": "Electrum Server",
"electrum_settings_explain": "Leave blank to use the default.",
"electrum_status": "Status",
"electrum_clear_alert_title": "Clear history?",
"electrum_preferred_server": "Preferred Server",
"electrum_preferred_server_description": "Enter the server you want your wallet to use for all Bitcoin activities. Once set, your wallet will exclusively use this server to check balances, send transactions, and fetch network data. Ensure you trust this server before setting it.", "electrum_clear_alert_title": "Clear history?",
"electrum_clear_alert_message": "Do you want to clear electrum servers history?",
"electrum_clear_alert_cancel": "Cancel",
"electrum_clear_alert_ok": "Ok",
"electrum_select": "Select",
"electrum_reset": "Reset to default",
"electrum_unable_to_connect": "Unable to connect to {server}.",
"electrum_history": "Server history",
"electrum_history": "History",
"electrum_reset_to_default": "Are you sure to want to reset your Electrum settings to default?",
"electrum_clear": "Clear",
"electrum_clear": "Clear History",
"encrypt_decrypt": "Decrypt Storage",
"encrypt_decrypt_q": "Are you sure you want to decrypt your storage? This will allow your wallets to be accessed without a password.",
"encrypt_enc_and_pass": "Encrypted and Password Protected",
@ -652,4 +651,4 @@
"notif_tx": "Notification transaction",
"not_found": "Payment code not found"
}
}
}

View File

@ -7,7 +7,7 @@ import navigationStyle, { CloseButtonPosition } from '../components/navigationSt
import { useTheme } from '../components/themes';
import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
import loc from '../loc';
import LNDViewAdditionalInvoiceInformation from '../screen/lnd/lndViewAdditionalInvoiceInformation';
import LNDViewAdditionalInvoiceInformation from '../screen/lnd/LNDViewAdditionalInvoiceInformation';
import LNDViewAdditionalInvoicePreImage from '../screen/lnd/lndViewAdditionalInvoicePreImage';
import LNDViewInvoice from '../screen/lnd/lndViewInvoice';
import LnurlAuth from '../screen/lnd/lnurlAuth';
@ -321,6 +321,7 @@ const DetailViewStackScreensStack = () => {
name="ElectrumSettings"
component={ElectrumSettingsComponent}
options={navigationStyle({ title: loc.settings.electrum_settings_server })(theme)}
initialParams={{ server: undefined }}
/>
<DetailViewStack.Screen
name="EncryptStorage"

View File

@ -1,4 +1,5 @@
import { LightningTransaction } from '../class/wallets/types';
import { ElectrumServerItem } from '../screen/settings/ElectrumSettings';
import { SendDetailsParams } from './SendDetailsStackParamList';
export type DetailViewStackParamList = {
@ -53,7 +54,7 @@ export type DetailViewStackParamList = {
NetworkSettings: undefined;
About: undefined;
DefaultView: undefined;
ElectrumSettings: undefined;
ElectrumSettings: { server?: ElectrumServerItem };
SettingsBlockExplorer: undefined;
EncryptStorage: undefined;
Language: undefined;

View File

@ -5,7 +5,7 @@ import { LazyLoadingIndicator } from './LazyLoadingIndicator';
const LNDCreateInvoice = lazy(() => import('../screen/lnd/lndCreateInvoice'));
const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet'));
const LNDViewInvoice = lazy(() => import('../screen/lnd/lndViewInvoice'));
const LNDViewAdditionalInvoiceInformation = lazy(() => import('../screen/lnd/lndViewAdditionalInvoiceInformation'));
const LNDViewAdditionalInvoiceInformation = lazy(() => import('../screen/lnd/LNDViewAdditionalInvoiceInformation'));
const LNDViewAdditionalInvoicePreImage = lazy(() => import('../screen/lnd/lndViewAdditionalInvoicePreImage'));
export const LNDCreateInvoiceComponent = () => (

View File

@ -11,7 +11,7 @@ const Licensing = lazy(() => import('../screen/settings/Licensing'));
const NetworkSettings = lazy(() => import('../screen/settings/NetworkSettings'));
const About = lazy(() => import('../screen/settings/About'));
const DefaultView = lazy(() => import('../screen/settings/DefaultView'));
const ElectrumSettings = lazy(() => import('../screen/settings/electrumSettings'));
const ElectrumSettings = lazy(() => import('../screen/settings/ElectrumSettings'));
const EncryptStorage = lazy(() => import('../screen/settings/EncryptStorage'));
const LightningSettings = lazy(() => import('../screen/settings/LightningSettings'));
const NotificationSettings = lazy(() => import('../screen/settings/NotificationSettings'));

View File

@ -9,7 +9,7 @@ export type PaymentCodeStackParamList = {
amount: number;
amountSats: number;
unit: BitcoinUnit;
noRbf: boolean;
isTransactionReplaceable: boolean;
launchedBy: string;
isEditable: boolean;
uri: string /* payjoin uri */;

View File

@ -17,6 +17,7 @@ import {
} from './LazyLoadSendDetailsStack';
import { SendDetailsStackParamList } from './SendDetailsStackParamList';
import HeaderRightButton from '../components/HeaderRightButton';
import { BitcoinUnit } from '../models/bitcoinUnits';
const Stack = createNativeStackNavigator<SendDetailsStackParamList>();
@ -37,7 +38,7 @@ const SendDetailsStack = () => {
statusBarStyle: 'light',
closeButtonPosition: CloseButtonPosition.Left,
})(theme)}
initialParams={{ isEditable: true }} // Correctly typed now
initialParams={{ isEditable: true, feeUnit: BitcoinUnit.BTC, amountUnit: BitcoinUnit.BTC }} // Correctly typed now
/>
<Stack.Screen
name="Confirm"

View File

@ -3,7 +3,12 @@ import { CreateTransactionTarget, CreateTransactionUtxo, TWallet } from '../clas
import { BitcoinUnit, Chain } from '../models/bitcoinUnits';
export type SendDetailsParams = {
memo?: string;
transactionMemo?: string;
isTransactionReplaceable?: boolean;
payjoinUrl?: string;
feeUnit?: BitcoinUnit;
frozenBalance?: number;
amountUnit?: BitcoinUnit;
address?: string;
amount?: number;
amountSats?: number;
@ -11,6 +16,7 @@ export type SendDetailsParams = {
noRbf?: boolean;
walletID: string;
launchedBy?: string;
utxos?: CreateTransactionUtxo[] | null;
isEditable?: boolean;
uri?: string;
addRecipientParams?: {
@ -74,7 +80,6 @@ export type SendDetailsStackParamList = {
};
CoinControl: {
walletID: string;
onUTXOChoose: (u: CreateTransactionUtxo[]) => void;
};
PaymentCodeList: {
walletID: string;

69
package-lock.json generated
View File

@ -17,11 +17,11 @@
"@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#839f2966cee77c0ad99d09609dadb61a338e7f54",
"@ngraveio/bc-ur": "1.1.13",
"@noble/secp256k1": "1.6.3",
"@react-native-async-storage/async-storage": "1.24.0",
"@react-native-clipboard/clipboard": "1.14.2",
"@react-native-async-storage/async-storage": "2.0.0",
"@react-native-clipboard/clipboard": "1.14.3",
"@react-native-community/push-notification-ios": "1.11.0",
"@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#a33379d",
"@react-native/gradle-plugin": "^0.75.4",
"@react-native/gradle-plugin": "0.75.4",
"@react-native/metro-config": "0.75.4",
"@react-navigation/drawer": "6.7.2",
"@react-navigation/native": "6.1.18",
@ -53,7 +53,7 @@
"electrum-mnemonic": "2.0.0",
"events": "3.3.0",
"junderw-crc32c": "1.2.0",
"lottie-react-native": "6.7.2",
"lottie-react-native": "7.0.0",
"path-browserify": "1.0.1",
"payjoin-client": "1.0.1",
"process": "0.11.10",
@ -66,7 +66,7 @@
"react-native-camera-kit": "13.0.0",
"react-native-crypto": "2.2.0",
"react-native-default-preference": "1.4.4",
"react-native-device-info": "11.1.0",
"react-native-device-info": "13.2.0",
"react-native-document-picker": "9.3.1",
"react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#3a61627",
"react-native-fs": "2.20.0",
@ -75,10 +75,10 @@
"react-native-haptic-feedback": "2.3.3",
"react-native-image-picker": "7.1.2",
"react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#e5c1217cd220bfab6e6d9a7c65838545082e3f8e",
"react-native-keychain": "8.2.0",
"react-native-keychain": "9.1.0",
"react-native-linear-gradient": "2.8.3",
"react-native-localize": "3.2.1",
"react-native-permissions": "4.1.5",
"react-native-permissions": "5.0.2",
"react-native-prompt-android": "github:BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376",
"react-native-push-notification": "8.1.1",
"react-native-qrcode-svg": "6.3.2",
@ -90,7 +90,7 @@
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
"react-native-screens": "3.34.0",
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
"react-native-share": "10.2.1",
"react-native-share": "11.0.4",
"react-native-svg": "15.8.0",
"react-native-tcp-socket": "6.2.0",
"react-native-vector-icons": "10.2.0",
@ -4507,21 +4507,21 @@
}
},
"node_modules/@react-native-async-storage/async-storage": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz",
"integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.0.0.tgz",
"integrity": "sha512-af6H9JjfL6G/PktBfUivvexoiFKQTJGQCtSWxMdivLzNIY94mu9DdiY0JqCSg/LyPCLGKhHPUlRQhNvpu3/KVA==",
"license": "MIT",
"dependencies": {
"merge-options": "^3.0.4"
},
"peerDependencies": {
"react-native": "^0.0.0-0 || >=0.60 <1.0"
"react-native": "^0.0.0-0 || >=0.65 <1.0"
}
},
"node_modules/@react-native-clipboard/clipboard": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.14.2.tgz",
"integrity": "sha512-Mb58f3neB6sM9oOtKYVGLvN8KVByea67OA9ekJ0c9FwdH24INu8RJoA7/fq+PRk+7oxbeamAcEoQPRv0uwbbMw==",
"version": "1.14.3",
"resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.14.3.tgz",
"integrity": "sha512-EVWxJfCSyBN2SH5b3JrA/w1qlYu3vihQOfdD7fs/BYp63xL6qy93CvbFDHzF8ooFpGM6f67hkAN+gxl1RfOKuw==",
"license": "MIT",
"workspaces": [
"example"
@ -17929,9 +17929,9 @@
}
},
"node_modules/lottie-react-native": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-6.7.2.tgz",
"integrity": "sha512-MZVx6N1EeO/EaSx8T44mJ0aHc5Mqee+xIfWwszni0oz8U2wlHdaWGjES44dHxaxgAp/0dRaFt3PkpZ6egTzcBg==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/lottie-react-native/-/lottie-react-native-7.0.0.tgz",
"integrity": "sha512-RnwacxdB1MKDS/WSX8XFyXw5nxEKF+aLYRzbkQBQY0pZTRF2XYg8zd25D1su1M0TEP0sgWutwN5rweSeCsf8qQ==",
"license": "Apache-2.0",
"peerDependencies": {
"@dotlottie/react-player": "^1.6.1",
@ -20593,9 +20593,9 @@
}
},
"node_modules/react-native-device-info": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-11.1.0.tgz",
"integrity": "sha512-hzXJSObJdezEz0hF7MAJ3tGeoesuQWenXXt9mrQR9Mjb8kXpZ09rqSsZ/quNpJdZpQ3rYiFa3/0GFG5KNn9PBg==",
"version": "13.2.0",
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-13.2.0.tgz",
"integrity": "sha512-VpTxHZsEZ7kes2alaZkB31278KuSPXfTZ4TmCCN77+bYxNnaHUDiBiQ1TSoKAOp51b7gZ/7EvM4McfgHofcTBQ==",
"license": "MIT",
"peerDependencies": {
"react-native": "*"
@ -20713,10 +20713,17 @@
}
},
"node_modules/react-native-keychain": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-8.2.0.tgz",
"integrity": "sha512-SkRtd9McIl1Ss2XSWNLorG+KMEbgeVqX+gV+t3u1EAAqT8q2/OpRmRbxpneT2vnb/dMhiU7g6K/pf3nxLUXRvA==",
"license": "MIT"
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/react-native-keychain/-/react-native-keychain-9.1.0.tgz",
"integrity": "sha512-txBVnhO/AVg+j6lrKe9cL/HPpb5BNHYYBRg/6lzbxKe8AsNqp639wq0nN8/IdkUCPjSezN8Pgp142HNomgqk4Q==",
"license": "MIT",
"workspaces": [
"KeychainExample",
"website"
],
"engines": {
"node": ">=18"
}
},
"node_modules/react-native-linear-gradient": {
"version": "2.8.3",
@ -20745,9 +20752,9 @@
}
},
"node_modules/react-native-permissions": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-4.1.5.tgz",
"integrity": "sha512-r6VMRacASmtRHS+GZ+5HQCp9p9kiE+UU9magHOZCXZLTJitdTuVHWZRrb4v4oqZGU+zAp3mZhTQftuMMv+WLUg==",
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-5.0.2.tgz",
"integrity": "sha512-CsL0jSGTJZET/Mq5l14ZcemQtPSj1Ru6LLO/J/T5RUQKwu9PavJqun7LX2te6yQ0tUoi+lA/etRHpggRVW+o0A==",
"license": "MIT",
"peerDependencies": {
"react": ">=18.1.0",
@ -20908,9 +20915,9 @@
"license": "ISC"
},
"node_modules/react-native-share": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-10.2.1.tgz",
"integrity": "sha512-Z2LWGYWH7raM4H6Oauttv1tEhaB43XSWJAN8iS6oaSG9CnyrUBeYFF4QpU1AH5RgNeylXQdN8CtbizCHHt6coQ==",
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/react-native-share/-/react-native-share-11.0.4.tgz",
"integrity": "sha512-i91n1Pcdaxxr39uxR5KduXjqi5FSEXuEO6rmeHl8OPs5rqo4No36qJXUU6du4TZUI6tFSENdxnzZMh3OsMF+ug==",
"license": "MIT",
"engines": {
"node": ">=16"

View File

@ -81,11 +81,11 @@
"@lodev09/react-native-true-sheet": "github:BlueWallet/react-native-true-sheet#839f2966cee77c0ad99d09609dadb61a338e7f54",
"@ngraveio/bc-ur": "1.1.13",
"@noble/secp256k1": "1.6.3",
"@react-native-async-storage/async-storage": "1.24.0",
"@react-native-clipboard/clipboard": "1.14.2",
"@react-native-async-storage/async-storage": "2.0.0",
"@react-native-clipboard/clipboard": "1.14.3",
"@react-native-community/push-notification-ios": "1.11.0",
"@react-native-menu/menu": "https://github.com/BlueWallet/menu.git#a33379d",
"@react-native/gradle-plugin": "^0.75.4",
"@react-native/gradle-plugin": "0.75.4",
"@react-native/metro-config": "0.75.4",
"@react-navigation/drawer": "6.7.2",
"@react-navigation/native": "6.1.18",
@ -117,7 +117,7 @@
"electrum-mnemonic": "2.0.0",
"events": "3.3.0",
"junderw-crc32c": "1.2.0",
"lottie-react-native": "6.7.2",
"lottie-react-native": "7.0.0",
"path-browserify": "1.0.1",
"payjoin-client": "1.0.1",
"process": "0.11.10",
@ -130,7 +130,7 @@
"react-native-camera-kit": "13.0.0",
"react-native-crypto": "2.2.0",
"react-native-default-preference": "1.4.4",
"react-native-device-info": "11.1.0",
"react-native-device-info": "13.2.0",
"react-native-document-picker": "9.3.1",
"react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#3a61627",
"react-native-fs": "2.20.0",
@ -139,10 +139,10 @@
"react-native-haptic-feedback": "2.3.3",
"react-native-image-picker": "7.1.2",
"react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#e5c1217cd220bfab6e6d9a7c65838545082e3f8e",
"react-native-keychain": "8.2.0",
"react-native-keychain": "9.1.0",
"react-native-linear-gradient": "2.8.3",
"react-native-localize": "3.2.1",
"react-native-permissions": "4.1.5",
"react-native-permissions": "5.0.2",
"react-native-prompt-android": "github:BlueWallet/react-native-prompt-android#ed168d66fed556bc2ed07cf498770f058b78a376",
"react-native-push-notification": "8.1.1",
"react-native-qrcode-svg": "6.3.2",
@ -154,7 +154,7 @@
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
"react-native-screens": "3.34.0",
"react-native-secure-key-store": "github:BlueWallet/react-native-secure-key-store#2076b4849e88aa0a78e08bfbb4ce3923e0925cbc",
"react-native-share": "10.2.1",
"react-native-share": "11.0.4",
"react-native-svg": "15.8.0",
"react-native-tcp-socket": "6.2.0",
"react-native-vector-icons": "10.2.0",

View File

@ -0,0 +1,110 @@
import React, { useEffect, useState } from 'react';
import { useRoute, RouteProp } from '@react-navigation/native';
import { Share, StyleSheet, View } from 'react-native';
import { BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents';
import presentAlert from '../../components/Alert';
import Button from '../../components/Button';
import CopyTextToClipboard from '../../components/CopyTextToClipboard';
import QRCodeComponent from '../../components/QRCodeComponent';
import SafeArea from '../../components/SafeArea';
import { useTheme } from '../../components/themes';
import loc from '../../loc';
import { useStorage } from '../../hooks/context/useStorage';
import { LightningCustodianWallet } from '../../class';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
type RouteParams = {
params: {
walletID: string;
};
};
const LNDViewAdditionalInvoiceInformation: React.FC = () => {
const { walletID } = useRoute<RouteProp<RouteParams>>().params;
const { wallets } = useStorage();
const wallet = wallets.find(w => w.getID() === walletID) as LightningCustodianWallet;
const [walletInfo, setWalletInfo] = useState<{ uris?: string[] } | undefined>();
const { colors } = useTheme();
const { goBack } = useExtendedNavigation();
const stylesHook = StyleSheet.create({
root: {
backgroundColor: colors.elevated,
},
});
useEffect(() => {
if (wallet) {
wallet
.fetchInfo()
.then(() => {
const info = wallet.info_raw;
// @ts-ignore: idk
if (info?.uris?.[0]) {
// @ts-ignore: idk
setWalletInfo(info);
} else {
presentAlert({ message: loc.errors.network });
goBack();
}
})
.catch((error: Error) => {
console.error(error);
presentAlert({ title: loc.errors.network, message: error.message });
goBack();
});
}
}, [wallet, goBack]);
return (
<SafeArea style={[styles.loading, stylesHook.root]}>
{!walletInfo ? (
<BlueLoading />
) : (
<View style={styles.wrapper}>
<View style={styles.qrcode}>
<QRCodeComponent value={walletInfo.uris?.[0] ?? ''} size={300} />
</View>
<BlueSpacing20 />
<BlueText>{loc.lndViewInvoice.open_direct_channel}</BlueText>
<CopyTextToClipboard text={walletInfo.uris?.[0] ?? ''} />
<View style={styles.share}>
<Button
icon={{
name: 'share-alternative',
type: 'entypo',
color: colors.buttonTextColor,
}}
onPress={async () => {
Share.share({
message: walletInfo.uris?.[0] ?? '',
});
}}
title={loc.receive.details_share}
/>
</View>
</View>
)}
</SafeArea>
);
};
const styles = StyleSheet.create({
loading: {
justifyContent: 'space-between',
alignItems: 'center',
},
wrapper: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
qrcode: {
justifyContent: 'center',
alignItems: 'center',
},
share: {
marginBottom: 25,
},
});
export default LNDViewAdditionalInvoiceInformation;

View File

@ -1,103 +0,0 @@
import { useNavigation, useRoute } from '@react-navigation/native';
import React, { useEffect, useState } from 'react';
import { Share, StyleSheet, View } from 'react-native';
import { BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents';
import presentAlert from '../../components/Alert';
import Button from '../../components/Button';
import CopyTextToClipboard from '../../components/CopyTextToClipboard';
import QRCodeComponent from '../../components/QRCodeComponent';
import SafeArea from '../../components/SafeArea';
import { useTheme } from '../../components/themes';
import loc from '../../loc';
import { useStorage } from '../../hooks/context/useStorage';
const LNDViewAdditionalInvoiceInformation = () => {
const { walletID } = useRoute().params;
const { wallets } = useStorage();
const wallet = wallets.find(w => w.getID() === walletID);
const [walletInfo, setWalletInfo] = useState();
const { colors } = useTheme();
const { goBack } = useNavigation();
const stylesHook = StyleSheet.create({
loading: {
backgroundColor: colors.elevated,
},
root: {
backgroundColor: colors.elevated,
},
});
useEffect(() => {
if (wallet) {
wallet
.fetchInfo()
.then(_ => {
setWalletInfo(wallet.info_raw);
})
.catch(error => {
console.log(error);
presentAlert({ message: loc.errors.network });
goBack();
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet]);
if (walletInfo === undefined) {
return (
<SafeArea style={[styles.loading, stylesHook.loading]}>
<BlueLoading />
</SafeArea>
);
}
return (
<SafeArea style={stylesHook.root}>
<View style={styles.wrapper}>
<View style={styles.qrcode}>
<QRCodeComponent value={walletInfo.uris[0]} size={300} />
</View>
<BlueSpacing20 />
<BlueText>{loc.lndViewInvoice.open_direct_channel}</BlueText>
<CopyTextToClipboard text={walletInfo.uris[0]} />
<View style={styles.share}>
<Button
icon={{
name: 'share-alternative',
type: 'entypo',
color: colors.buttonTextColor,
}}
onPress={async () => {
Share.share({
message: walletInfo.uris[0],
});
}}
title={loc.receive.details_share}
/>
</View>
</View>
</SafeArea>
);
};
const styles = StyleSheet.create({
loading: {
justifyContent: 'space-between',
alignItems: 'center',
},
wrapper: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
qrcode: {
justifyContent: 'center',
alignItems: 'center',
},
share: {
marginBottom: 25,
},
});
export default LNDViewAdditionalInvoiceInformation;

View File

@ -81,6 +81,13 @@ const SendDetails = () => {
const setParams = navigation.setParams;
const route = useRoute<RouteProps>();
const name = route.name;
const feeUnit = route.params?.feeUnit ?? BitcoinUnit.BTC;
const amountUnit = route.params?.amountUnit ?? BitcoinUnit.BTC;
const frozenBalance = route.params?.frozenBalance ?? 0;
const transactionMemo = route.params?.transactionMemo;
const utxos = route.params?.utxos;
const payjoinUrl = route.params?.payjoinUrl;
const isTransactionReplaceable = route.params?.isTransactionReplaceable;
const routeParams = route.params;
const scrollView = useRef<FlatList<any>>(null);
const scrollIndex = useRef(0);
@ -92,26 +99,18 @@ const SendDetails = () => {
const [isLoading, setIsLoading] = useState(false);
const [wallet, setWallet] = useState<TWallet | null>(null);
const feeModalRef = useRef<BottomModalHandle>(null);
const [walletSelectionOrCoinsSelectedHidden, setWalletSelectionOrCoinsSelectedHidden] = useState(false);
const [isAmountToolbarVisibleForAndroid, setIsAmountToolbarVisibleForAndroid] = useState(false);
const [isTransactionReplaceable, setIsTransactionReplaceable] = useState<boolean | undefined>(false);
const { isVisible } = useKeyboard();
const [addresses, setAddresses] = useState<IPaymentDestinations[]>([]);
const [units, setUnits] = useState<BitcoinUnit[]>([]);
const [transactionMemo, setTransactionMemo] = useState<string>('');
const [networkTransactionFees, setNetworkTransactionFees] = useState(new NetworkTransactionFee(3, 2, 1));
const [networkTransactionFeesIsLoading, setNetworkTransactionFeesIsLoading] = useState(false);
const [customFee, setCustomFee] = useState<string | null>(null);
const [feePrecalc, setFeePrecalc] = useState<IFee>({ current: null, slowFee: null, mediumFee: null, fastestFee: null });
const [feeUnit, setFeeUnit] = useState<BitcoinUnit>();
const [amountUnit, setAmountUnit] = useState<BitcoinUnit>();
const [utxo, setUtxo] = useState<CreateTransactionUtxo[] | null>(null);
const [frozenBalance, setFrozenBlance] = useState<number>(0);
const [payjoinUrl, setPayjoinUrl] = useState<string | null>(null);
const [changeAddress, setChangeAddress] = useState<string | null>(null);
const [dumb, setDumb] = useState(false);
const { isEditable } = routeParams;
// if utxo is limited we use it to calculate available balance
const balance: number = utxo ? utxo.reduce((prev, curr) => prev + curr.value, 0) : (wallet?.getBalance() ?? 0);
const balance: number = utxos ? utxos.reduce((prev, curr) => prev + curr.value, 0) : (wallet?.getBalance() ?? 0);
const allBalance = formatBalanceWithoutSuffix(balance, BitcoinUnit.BTC, true);
// if cutomFee is not set, we need to choose highest possible fee for wallet balance
@ -138,17 +137,6 @@ const SendDetails = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colors, wallet, isTransactionReplaceable, balance, addresses, isEditable, isLoading]);
useKeyboard({
onKeyboardDidShow: () => {
setWalletSelectionOrCoinsSelectedHidden(true);
setIsAmountToolbarVisibleForAndroid(true);
},
onKeyboardDidHide: () => {
setWalletSelectionOrCoinsSelectedHidden(false);
setIsAmountToolbarVisibleForAndroid(false);
},
});
useEffect(() => {
// decode route params
const currentAddress = addresses[scrollIndex.current];
@ -176,10 +164,9 @@ const SendDetails = () => {
});
if (memo?.trim().length > 0) {
setTransactionMemo(memo);
setParams({ transactionMemo: memo });
}
setAmountUnit(BitcoinUnit.BTC);
setPayjoinUrl(pjUrl);
setParams({ payjoinUrl: pjUrl, amountUnit: BitcoinUnit.BTC });
} catch (error) {
console.log(error);
presentAlert({ title: loc.errors.error, message: loc.send.details_error_decode });
@ -196,9 +183,6 @@ const SendDetails = () => {
return [...value, { address: routeParams.address, key: String(Math.random()), amount, amountSats }];
}
});
if (routeParams.memo && routeParams.memo?.trim().length > 0) {
setTransactionMemo(routeParams.memo);
}
setUnits(u => {
u[scrollIndex.current] = unit;
return [...u];
@ -238,8 +222,7 @@ const SendDetails = () => {
}
const newWallet = (routeParams.walletID && wallets.find(w => w.getID() === routeParams.walletID)) || suitable[0];
setWallet(newWallet);
setFeeUnit(newWallet.getPreferredBalanceUnit());
setAmountUnit(newWallet.preferredBalanceUnit); // default for whole screen
setParams({ feeUnit: newWallet.getPreferredBalanceUnit(), amountUnit: newWallet.getPreferredBalanceUnit() });
// we are ready!
setIsLoading(false);
@ -276,9 +259,11 @@ const SendDetails = () => {
setSelectedWalletID(wallet.getID());
// reset other values
setUtxo(null);
setChangeAddress(null);
setIsTransactionReplaceable(wallet.type === HDSegwitBech32Wallet.type && !routeParams.noRbf ? true : undefined);
setParams({
utxos: null,
isTransactionReplaceable: wallet.type === HDSegwitBech32Wallet.type && !routeParams.isTransactionReplaceable ? true : undefined,
});
// update wallet UTXO
wallet
.fetchUtxo()
@ -294,9 +279,9 @@ const SendDetails = () => {
if (!wallet) return; // wait for it
const fees = networkTransactionFees;
const requestedSatPerByte = Number(feeRate);
const lutxo = utxo || wallet.getUtxo();
const lutxo = utxos || wallet.getUtxo();
let frozen = 0;
if (!utxo) {
if (!utxos) {
// if utxo is not limited search for frozen outputs and calc it's balance
frozen = wallet
.getUtxo(true)
@ -369,8 +354,8 @@ const SendDetails = () => {
}
setFeePrecalc(newFeePrecalc);
setFrozenBlance(frozen);
}, [wallet, networkTransactionFees, utxo, addresses, feeRate, dumb]); // eslint-disable-line react-hooks/exhaustive-deps
setParams({ frozenBalance: frozen });
}, [wallet, networkTransactionFees, utxos, addresses, feeRate, dumb]); // eslint-disable-line react-hooks/exhaustive-deps
// we need to re-calculate fees if user opens-closes coin control
useFocusEffect(
@ -470,9 +455,7 @@ const SendDetails = () => {
u[scrollIndex.current] = BitcoinUnit.BTC; // also resetting current unit to BTC
return [...u];
});
setTransactionMemo(options.label || ''); // there used to be `options.message` here as well. bug?
setAmountUnit(BitcoinUnit.BTC);
setPayjoinUrl(options.pj || '');
setParams({ transactionMemo: options.label || '', amountUnit: BitcoinUnit.BTC, payjoinUrl: options.pj || '' }); // there used to be `options.message` here as well. bug?
// RN Bug: contentOffset gets reset to 0 when state changes. Remove code once this bug is resolved.
setTimeout(() => scrollView.current?.scrollToIndex({ index: currentIndex, animated: false }), 50);
}
@ -565,7 +548,7 @@ const SendDetails = () => {
const change = await getChangeAddressAsync();
assert(change, 'Could not get change address');
const requestedSatPerByte = Number(feeRate);
const lutxo: CreateTransactionUtxo[] = utxo || (wallet?.getUtxo() ?? []);
const lutxo: CreateTransactionUtxo[] = utxos || (wallet?.getUtxo() ?? []);
console.log({ requestedSatPerByte, lutxo: lutxo.length });
const targets: CreateTransactionTarget[] = [];
@ -669,6 +652,10 @@ const SendDetails = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [routeParams.walletID]);
const setTransactionMemo = (memo: string) => {
setParams({ transactionMemo: memo });
};
/**
* same as `importTransaction`, but opens camera instead.
*
@ -898,7 +885,6 @@ const SendDetails = () => {
if (!wallet) return;
navigation.navigate('CoinControl', {
walletID: wallet?.getID(),
onUTXOChoose: (u: CreateTransactionUtxo[]) => setUtxo(u),
});
};
@ -1050,7 +1036,7 @@ const SendDetails = () => {
};
const onReplaceableFeeSwitchValueChanged = (value: boolean) => {
setIsTransactionReplaceable(value);
setParams({ isTransactionReplaceable: value });
};
const handleRecipientsScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
@ -1141,16 +1127,16 @@ const SendDetails = () => {
};
const renderWalletSelectionOrCoinsSelected = () => {
if (walletSelectionOrCoinsSelectedHidden) return null;
if (utxo !== null) {
if (isVisible) return null;
if (utxos !== null) {
return (
<View style={styles.select}>
<CoinsSelected
number={utxo.length}
number={utxos?.length || 0}
onContainerPress={handleCoinControl}
onClose={() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setUtxo(null);
setParams({ utxos: null });
}}
/>
</View>
@ -1262,9 +1248,11 @@ const SendDetails = () => {
addrs[index] = item;
return [...addrs];
});
setTransactionMemo(memo || transactionMemo);
if (memo) {
setParams({ transactionMemo: memo });
}
setIsLoading(false);
setPayjoinUrl(pjUrl);
setParams({ payjoinUrl: pjUrl });
}}
onBarScanned={processAddressData}
address={item.address}
@ -1354,7 +1342,7 @@ const SendDetails = () => {
<DismissKeyboardInputAccessory />
{Platform.select({
ios: <InputAccessoryAllFunds canUseAll={balance > 0} onUseAllPressed={onUseAllPressed} balance={String(allBalance)} />,
android: isAmountToolbarVisibleForAndroid && (
android: isVisible && (
<InputAccessoryAllFunds canUseAll={balance > 0} onUseAllPressed={onUseAllPressed} balance={String(allBalance)} />
),
})}

View File

@ -260,7 +260,7 @@ const CoinControl = () => {
const navigation = useExtendedNavigation();
const { width } = useWindowDimensions();
const bottomModalRef = useRef(null);
const { walletID, onUTXOChoose } = useRoute().params;
const { walletID } = useRoute().params;
const { wallets, saveToDisk, sleep } = useStorage();
const wallet = wallets.find(w => w.getID() === walletID);
// sort by height ascending, txid , vout ascending
@ -329,8 +329,13 @@ const CoinControl = () => {
const handleUseCoin = async u => {
setOutput(null);
navigation.pop();
onUTXOChoose(u);
navigation.navigate('SendDetailsRoot', {
screen: 'SendDetails',
params: {
utxos: u,
},
merge: true,
});
};
const handleMassFreeze = () => {
@ -476,7 +481,7 @@ const styles = StyleSheet.create({
padding: {
padding: 16,
},
modalMinHeight: Platform.OS === 'android' ? { minHeight: 490 } : {},
modalMinHeight: Platform.OS === 'android' ? { minHeight: 530 } : {},
empty: {
flex: 1,
justifyContent: 'center',

View File

@ -0,0 +1,536 @@
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { Alert, Keyboard, LayoutAnimation, Platform, ScrollView, StyleSheet, Switch, TextInput, View } from 'react-native';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { BlueCard, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
import presentAlert from '../../components/Alert';
import Button from '../../components/Button';
import { scanQrHelper } from '../../helpers/scan-qr';
import loc from '../../loc';
import {
DoneAndDismissKeyboardInputAccessory,
DoneAndDismissKeyboardInputAccessoryViewID,
} from '../../components/DoneAndDismissKeyboardInputAccessory';
import DefaultPreference from 'react-native-default-preference';
import { DismissKeyboardInputAccessory, DismissKeyboardInputAccessoryViewID } from '../../components/DismissKeyboardInputAccessory';
import { useTheme } from '../../components/themes';
import { RouteProp, useRoute } from '@react-navigation/native';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
import { Divider } from '@rneui/themed';
import { Header } from '../../components/Header';
import AddressInput from '../../components/AddressInput';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { GROUP_IO_BLUEWALLET } from '../../blue_modules/currency';
import { Action } from '../../components/types';
import { useStorage } from '../../hooks/context/useStorage';
import ListItem, { PressableWrapper } from '../../components/ListItem';
import HeaderMenuButton from '../../components/HeaderMenuButton';
type RouteProps = RouteProp<DetailViewStackParamList, 'ElectrumSettings'>;
export interface ElectrumServerItem {
host: string;
port?: number;
sslPort?: number;
}
const ElectrumSettings: React.FC = () => {
const { colors } = useTheme();
const { server } = useRoute<RouteProps>().params;
const { setOptions } = useExtendedNavigation();
const [isLoading, setIsLoading] = useState(true);
const [isOfflineMode, setIsOfflineMode] = useState(true);
const [serverHistory, setServerHistory] = useState<ElectrumServerItem[]>([]);
const [config, setConfig] = useState<{ connected?: number; host?: string; port?: string }>({});
const [host, setHost] = useState<string>('');
const [port, setPort] = useState<number | undefined>();
const [sslPort, setSslPort] = useState<number | undefined>(undefined);
const [isAndroidNumericKeyboardFocused, setIsAndroidNumericKeyboardFocused] = useState(false);
const [isAndroidAddressKeyboardVisible, setIsAndroidAddressKeyboardVisible] = useState(false);
const { setIsElectrumDisabled } = useStorage();
const stylesHook = StyleSheet.create({
inputWrap: {
borderColor: colors.formBorder,
backgroundColor: colors.inputBackgroundColor,
},
containerConnected: {
backgroundColor: colors.feeLabel,
},
containerDisconnected: {
backgroundColor: colors.redBG,
},
textConnected: {
color: colors.feeValue,
},
textDisconnected: {
color: colors.redText,
},
hostname: {
color: colors.foregroundColor,
},
inputText: {
color: colors.foregroundColor,
},
usePort: {
color: colors.foregroundColor,
},
});
useEffect(() => {
let configInterval: NodeJS.Timeout | null = null;
const fetchData = async () => {
const savedHost = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_HOST);
const savedPort = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_TCP_PORT);
const savedSslPort = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_SSL_PORT);
const serverHistoryStr = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_SERVER_HISTORY);
const offlineMode = await BlueElectrum.isDisabled();
const parsedServerHistory: ElectrumServerItem[] = serverHistoryStr ? JSON.parse(serverHistoryStr) : [];
setHost(savedHost || '');
setPort(savedPort ? Number(savedPort) : undefined);
setSslPort(savedSslPort ? Number(savedSslPort) : undefined);
setServerHistory(parsedServerHistory);
setIsOfflineMode(offlineMode);
setConfig(await BlueElectrum.getConfig());
configInterval = setInterval(async () => {
setConfig(await BlueElectrum.getConfig());
}, 500);
setIsLoading(false);
};
fetchData();
return () => {
if (configInterval) clearInterval(configInterval);
};
}, []);
useEffect(() => {
if (server) {
triggerHapticFeedback(HapticFeedbackTypes.ImpactHeavy);
Alert.alert(
loc.formatString(loc.settings.set_electrum_server_as_default, { server: (server as ElectrumServerItem).host }),
'',
[
{
text: loc._.ok,
onPress: () => {
onBarScanned(JSON.stringify(server));
},
style: 'default',
},
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
}
}, [server]);
const clearHistory = useCallback(async () => {
setIsLoading(true);
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify([]));
setServerHistory([]);
setIsLoading(false);
}, []);
const serverExists = useCallback(
(value: ElectrumServerItem) => {
return serverHistory.some(s => `${s.host}:${s.port}:${s.sslPort}` === `${value.host}:${value.port}:${value.sslPort}`);
},
[serverHistory],
);
const save = useCallback(async () => {
Keyboard.dismiss();
setIsLoading(true);
try {
if (!host && !port && !sslPort) {
await AsyncStorage.removeItem(BlueElectrum.ELECTRUM_HOST);
await AsyncStorage.removeItem(BlueElectrum.ELECTRUM_TCP_PORT);
await AsyncStorage.removeItem(BlueElectrum.ELECTRUM_SSL_PORT);
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
await DefaultPreference.clear(BlueElectrum.ELECTRUM_HOST);
await DefaultPreference.clear(BlueElectrum.ELECTRUM_TCP_PORT);
await DefaultPreference.clear(BlueElectrum.ELECTRUM_SSL_PORT);
} else {
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_HOST, host);
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_TCP_PORT, port?.toString() || '');
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SSL_PORT, sslPort?.toString() || '');
if (!serverExists({ host, port, sslPort })) {
const newServerHistory = [...serverHistory, { host, port, sslPort }];
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify(newServerHistory));
setServerHistory(newServerHistory);
}
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
await DefaultPreference.set(BlueElectrum.ELECTRUM_HOST, host);
await DefaultPreference.set(BlueElectrum.ELECTRUM_TCP_PORT, port?.toString() || '');
await DefaultPreference.set(BlueElectrum.ELECTRUM_SSL_PORT, sslPort?.toString() || '');
}
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
presentAlert({ message: loc.settings.electrum_saved });
} catch (error) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: (error as Error).message });
}
setIsLoading(false);
}, [host, port, sslPort, serverExists, serverHistory]);
const resetToDefault = useCallback(() => {
Alert.alert(loc.settings.electrum_reset, loc.settings.electrum_reset_to_default, [
{
text: loc._.cancel,
onPress: () => console.log('Cancel Pressed'),
style: 'cancel',
},
{
text: loc._.ok,
style: 'destructive',
onPress: async () => {
setHost('');
setPort(undefined);
setSslPort(undefined);
await save();
},
},
]);
}, [save]);
const selectServer = useCallback(
(value: string) => {
const parsedServer = JSON.parse(value) as ElectrumServerItem;
setHost(parsedServer.host);
setPort(parsedServer.port);
setSslPort(parsedServer.sslPort);
save();
},
[save],
);
const clearHistoryAlert = useCallback(() => {
triggerHapticFeedback(HapticFeedbackTypes.ImpactHeavy);
Alert.alert(loc.settings.electrum_clear_alert_title, loc.settings.electrum_clear_alert_message, [
{ text: loc.settings.electrum_clear_alert_cancel, onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
{ text: loc.settings.electrum_clear_alert_ok, onPress: () => clearHistory() },
]);
}, [clearHistory]);
const onPressMenuItem = useCallback(
(id: string) => {
switch (id) {
case CommonToolTipActions.ResetToDefault.id:
resetToDefault();
break;
case CommonToolTipActions.ClearHistory.id:
clearHistoryAlert();
break;
default:
try {
selectServer(id);
} catch (error) {
console.warn('Unknown menu item selected:', id);
}
break;
}
},
[clearHistoryAlert, resetToDefault, selectServer],
);
const toolTipActions = useMemo(() => {
const actions: Action[] = [CommonToolTipActions.ResetToDefault];
if (serverHistory.length > 0) {
const serverSubactions: Action[] = serverHistory.map(value => ({
id: JSON.stringify(value),
text: `${value.host}`,
subtitle: `${value.port || value.sslPort}`,
menuState: `${host}:${port}:${sslPort}` === `${value.host}:${value.port}:${value.sslPort}`,
disabled: isLoading || (host === value.host && (port === value.port || sslPort === value.sslPort)),
}));
actions.push({
id: 'server_history',
text: loc.settings.electrum_history,
subactions: [CommonToolTipActions.ClearHistory, ...serverSubactions],
});
}
return actions;
}, [host, isLoading, port, serverHistory, sslPort]);
const HeaderRight = useMemo(
() => <HeaderMenuButton actions={toolTipActions} onPressMenuItem={onPressMenuItem} />,
[onPressMenuItem, toolTipActions],
);
useEffect(() => {
setOptions({
headerRight: isOfflineMode ? null : () => HeaderRight,
});
}, [HeaderRight, isOfflineMode, setOptions]);
const checkServer = async () => {
setIsLoading(true);
try {
const features = await BlueElectrum.serverFeatures();
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
presentAlert({ message: JSON.stringify(features, null, 2) });
} catch (error) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: (error as Error).message });
}
setIsLoading(false);
};
const onBarScanned = (value: string) => {
let v = value;
if (value && DeeplinkSchemaMatch.getServerFromSetElectrumServerAction(value)) {
v = DeeplinkSchemaMatch.getServerFromSetElectrumServerAction(value) as string;
}
const [scannedHost, scannedPort, type] = v?.split(':') ?? [];
setHost(scannedHost);
if (type === 's') {
setSslPort(Number(scannedPort));
setPort(undefined);
} else {
setPort(Number(scannedPort));
setSslPort(undefined);
}
};
const importScan = async () => {
const scanned = await scanQrHelper('ElectrumSettings', true);
if (scanned) {
onBarScanned(scanned);
}
};
const onSSLPortChange = (value: boolean) => {
Keyboard.dismiss();
if (value) {
setPort(undefined);
setSslPort(port);
} else {
setPort(sslPort);
setSslPort(undefined);
}
};
const onElectrumConnectionEnabledSwitchChange = async (value: boolean) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
if (value) {
await BlueElectrum.setDisabled(true);
setIsElectrumDisabled(true);
BlueElectrum.forceDisconnect();
} else {
await BlueElectrum.setDisabled(false);
setIsElectrumDisabled(false);
BlueElectrum.connectMain();
}
setIsOfflineMode(value);
};
const renderElectrumSettings = () => {
return (
<>
<Divider />
<BlueSpacing20 />
<Header leftText={loc.settings.electrum_status} />
<BlueSpacing20 />
<BlueCard>
<View style={styles.connectWrap}>
<View style={[styles.container, config.connected === 1 ? stylesHook.containerConnected : stylesHook.containerDisconnected]}>
<BlueText
style={[styles.textConnectionStatus, config.connected === 1 ? stylesHook.textConnected : stylesHook.textDisconnected]}
>
{config.connected === 1 ? loc.settings.electrum_connected : loc.settings.electrum_connected_not}
</BlueText>
</View>
</View>
<BlueSpacing10 />
<BlueText style={[styles.hostname, stylesHook.hostname]} onPress={checkServer} selectable>
{config.host}:{config.port}
</BlueText>
</BlueCard>
<BlueSpacing20 />
<Divider />
<BlueSpacing10 />
<BlueSpacing20 />
<Header leftText={loc.settings.electrum_preferred_server} />
<BlueCard>
<BlueText>{loc.settings.electrum_preferred_server_description}</BlueText>
<BlueSpacing20 />
<AddressInput
testID="HostInput"
placeholder={loc.formatString(loc.settings.electrum_host, { example: '10.20.30.40' })}
address={host}
onChangeText={text => setHost(text.trim())}
editable={!isLoading}
onBarScanned={importScan}
keyboardType="default"
onBlur={() => setIsAndroidAddressKeyboardVisible(false)}
onFocus={() => setIsAndroidAddressKeyboardVisible(true)}
inputAccessoryViewID={DoneAndDismissKeyboardInputAccessoryViewID}
isLoading={isLoading}
/>
<BlueSpacing20 />
<View style={styles.portWrap}>
<View style={[styles.inputWrap, stylesHook.inputWrap]}>
<TextInput
placeholder={loc.formatString(loc.settings.electrum_port, { example: '50001' })}
value={sslPort?.toString() === '' || sslPort === undefined ? port?.toString() || '' : sslPort?.toString() || ''}
onChangeText={text => {
const parsed = Number(text.trim());
if (Number.isNaN(parsed)) {
// Handle invalid input
sslPort === undefined ? setPort(undefined) : setSslPort(undefined);
return;
}
sslPort === undefined ? setPort(parsed) : setSslPort(parsed);
}}
numberOfLines={1}
style={[styles.inputText, stylesHook.inputText]}
editable={!isLoading}
placeholderTextColor="#81868e"
underlineColorAndroid="transparent"
autoCorrect={false}
autoCapitalize="none"
keyboardType="number-pad"
inputAccessoryViewID={DismissKeyboardInputAccessoryViewID}
testID="PortInput"
onFocus={() => setIsAndroidNumericKeyboardFocused(true)}
onBlur={() => setIsAndroidNumericKeyboardFocused(false)}
/>
</View>
<BlueText style={[styles.usePort, stylesHook.usePort]}>{loc.settings.use_ssl}</BlueText>
<Switch
testID="SSLPortInput"
value={sslPort !== undefined}
onValueChange={onSSLPortChange}
disabled={host?.endsWith('.onion') ?? false}
/>
</View>
</BlueCard>
<BlueCard>
<BlueSpacing20 />
<Button showActivityIndicator={isLoading} disabled={isLoading} testID="Save" onPress={save} title={loc.settings.save} />
</BlueCard>
{Platform.select({
ios: <DismissKeyboardInputAccessory />,
android: isAndroidNumericKeyboardFocused && <DismissKeyboardInputAccessory />,
})}
{Platform.select({
ios: (
<DoneAndDismissKeyboardInputAccessory
onClearTapped={() => setHost('')}
onPasteTapped={text => {
setHost(text);
Keyboard.dismiss();
}}
/>
),
android: isAndroidAddressKeyboardVisible && (
<DoneAndDismissKeyboardInputAccessory
onClearTapped={() => {
setHost('');
Keyboard.dismiss();
}}
onPasteTapped={text => {
setHost(text);
Keyboard.dismiss();
}}
/>
),
})}
</>
);
};
return (
<ScrollView
keyboardShouldPersistTaps="always"
automaticallyAdjustContentInsets
contentInsetAdjustmentBehavior="automatic"
automaticallyAdjustKeyboardInsets
testID="ElectrumSettingsScrollView"
>
<ListItem
Component={PressableWrapper}
title={loc.settings.electrum_offline_mode}
switch={{
onValueChange: onElectrumConnectionEnabledSwitchChange,
value: isOfflineMode,
testID: 'ElectrumConnectionEnabledSwitch',
}}
disabled={isLoading}
bottomDivider={false}
subtitle={loc.settings.electrum_offline_description}
/>
{!isOfflineMode && renderElectrumSettings()}
</ScrollView>
);
};
const styles = StyleSheet.create({
connectWrap: {
width: 'auto',
height: 34,
flexWrap: 'wrap',
justifyContent: 'center',
flexDirection: 'row',
},
hostname: {
textAlign: 'center',
},
container: {
paddingTop: 6,
paddingBottom: 6,
paddingLeft: 16,
paddingRight: 16,
borderRadius: 20,
},
inputWrap: {
flex: 1,
flexDirection: 'row',
borderWidth: 1,
borderBottomWidth: 0.5,
minHeight: 44,
height: 44,
alignItems: 'center',
borderRadius: 4,
},
portWrap: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
inputText: {
flex: 1,
marginHorizontal: 8,
minHeight: 36,
height: 36,
},
textConnectionStatus: {
fontWeight: 'bold',
},
usePort: {
marginHorizontal: 16,
},
});
export default ElectrumSettings;

View File

@ -3,7 +3,7 @@ import { I18nManager, Linking, ScrollView, StyleSheet, TextInput, View, Pressabl
import { Button as ButtonRNElements } from '@rneui/themed';
// @ts-ignore: no declaration file
import Notifications from '../../blue_modules/notifications';
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
import { BlueCard, BlueSpacing20, BlueSpacing40, BlueText } from '../../BlueComponents';
import presentAlert from '../../components/Alert';
import { Button } from '../../components/Button';
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
@ -123,6 +123,10 @@ const NotificationSettings: React.FC = () => {
setIsLoading(false);
}, [URI]);
const onSystemSettings = () => {
Linking.openSettings();
};
return (
<ScrollView style={stylesWithThemeHook.scroll} automaticallyAdjustContentInsets contentInsetAdjustmentBehavior="automatic">
<ListItem
@ -193,6 +197,8 @@ const NotificationSettings: React.FC = () => {
</BlueCard>
</>
)}
<BlueSpacing40 />
<ListItem title={loc.settings.privacy_system_settings} onPress={onSystemSettings} chevron />
</ScrollView>
);
};

View File

@ -1,542 +0,0 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import {
Alert,
Keyboard,
Platform,
Pressable,
ScrollView,
StyleSheet,
Switch,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import DefaultPreference from 'react-native-default-preference';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { BlueButtonLink, BlueCard, BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
import presentAlert, { AlertType } from '../../components/Alert';
import Button from '../../components/Button';
import ListItem from '../../components/ListItem';
import { BlueCurrentTheme } from '../../components/themes';
import { scanQrHelper } from '../../helpers/scan-qr';
import loc from '../../loc';
import { StorageContext } from '../../components/Context/StorageProvider';
import {
DoneAndDismissKeyboardInputAccessory,
DoneAndDismissKeyboardInputAccessoryViewID,
} from '../../components/DoneAndDismissKeyboardInputAccessory';
import { DismissKeyboardInputAccessory, DismissKeyboardInputAccessoryViewID } from '../../components/DismissKeyboardInputAccessory';
export default class ElectrumSettings extends Component {
static contextType = StorageContext;
constructor(props) {
super(props);
const server = props?.route?.params?.server;
this.state = {
isLoading: true,
isOfflineMode: false,
serverHistory: [],
config: {},
server,
sslPort: '',
port: '',
};
}
componentWillUnmount() {
clearInterval(this.state.inverval);
}
async componentDidMount() {
const host = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_HOST);
const port = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_TCP_PORT);
const sslPort = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_SSL_PORT);
const serverHistoryStr = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_SERVER_HISTORY);
const isOfflineMode = await BlueElectrum.isDisabled();
const serverHistory = JSON.parse(serverHistoryStr) || [];
this.setState({
isLoading: false,
host,
port,
sslPort,
serverHistory,
isOfflineMode,
isAndroidNumericKeyboardFocused: false,
isAndroidAddressKeyboardVisible: false,
});
const inverval = setInterval(async () => {
this.setState({
config: await BlueElectrum.getConfig(),
});
}, 500);
this.setState({
config: await BlueElectrum.getConfig(),
inverval,
});
if (this.state.server) {
triggerHapticFeedback(HapticFeedbackTypes.ImpactHeavy);
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 () => {
this.setState({ isLoading: true }, async () => {
const features = await BlueElectrum.serverFeatures();
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
presentAlert({ message: JSON.stringify(features, null, 2) });
this.setState({ isLoading: false });
});
};
selectServer = async server => {
this.setState({ host: server.host, port: server.port, sslPort: server.sslPort }, () => {
this.save();
});
};
clearHistoryAlert() {
triggerHapticFeedback(HapticFeedbackTypes.ImpactHeavy);
Alert.alert(loc.settings.electrum_clear_alert_title, loc.settings.electrum_clear_alert_message, [
{ text: loc.settings.electrum_clear_alert_cancel, onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
{ text: loc.settings.electrum_clear_alert_ok, onPress: () => this.clearHistory() },
]);
}
clearHistory = async () => {
this.setState({ isLoading: true }, async () => {
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify([]));
this.setState({
serverHistory: [],
isLoading: false,
});
});
};
resetToDefault = async () => {
this.setState({ port: '', host: '', sslPort: '' }, () => {
this.save();
});
};
serverExists = server => {
const { serverHistory } = this.state;
return serverHistory.some(s => {
return `${s.host}${s.port}${s.sslPort}` === `${server.host}${server.port}${server.sslPort}`;
});
};
save = () => {
const host = this.state.host ? this.state.host : '';
const port = this.state.port ? this.state.port : '';
const sslPort = this.state.sslPort ? this.state.sslPort : '';
const serverHistory = this.state.serverHistory || [];
this.setState({ isLoading: true }, async () => {
try {
if (!host && !port && !sslPort) {
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_HOST, '');
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_TCP_PORT, '');
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SSL_PORT, '');
try {
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
await DefaultPreference.clear(BlueElectrum.ELECTRUM_HOST);
await DefaultPreference.clear(BlueElectrum.ELECTRUM_SSL_PORT);
await DefaultPreference.clear(BlueElectrum.ELECTRUM_TCP_PORT);
} catch (e) {
// Must be running on Android
console.log(e);
}
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
presentAlert({ message: loc.settings.electrum_saved });
} else if (!(await BlueElectrum.testConnection(host, port, sslPort))) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: loc.settings.electrum_error_connect });
} else {
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_HOST, host);
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_TCP_PORT, port);
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SSL_PORT, sslPort);
if (!this.serverExists({ host, port, sslPort })) {
serverHistory.push({
host,
port,
sslPort,
});
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify(serverHistory));
}
try {
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
await DefaultPreference.set(BlueElectrum.ELECTRUM_HOST, host);
await DefaultPreference.set(BlueElectrum.ELECTRUM_TCP_PORT, port);
await DefaultPreference.set(BlueElectrum.ELECTRUM_SSL_PORT, sslPort);
} catch (e) {
// Must be running on Android
console.log(e);
}
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
presentAlert({ message: loc.settings.electrum_saved });
}
} catch (error) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: error, type: AlertType.Toast });
}
this.setState({ isLoading: false });
});
};
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);
}
const [host, port, type] = value.split(':');
this.setState({ host, sslPort: '', port: '' }, () => {
type === 's' ? this.setState({ sslPort: port }) : this.setState({ port });
});
};
importScan = async () => {
const scanned = await scanQrHelper('ElectrumSettings', true);
this.onBarScanned(scanned);
};
useSSLPortToggled = value => {
switch (value) {
case true:
this.setState(prevState => {
return { port: '', sslPort: prevState.port };
});
break;
case false:
this.setState(prevState => {
return { port: prevState.sslPort, sslPort: '' };
});
break;
}
};
onElectrumConnectionEnabledSwitchValueChanged = async value => {
if (value === true) {
await BlueElectrum.setDisabled(true);
this.context.setIsElectrumDisabled(true);
BlueElectrum.forceDisconnect();
} else {
await BlueElectrum.setDisabled(false);
this.context.setIsElectrumDisabled(false);
BlueElectrum.connectMain();
}
this.setState({ isOfflineMode: value });
};
renderElectrumSettings = () => {
const serverHistoryItems = this.state.serverHistory.map((server, i) => {
return (
<View key={i} style={styles.serverHistoryItem}>
<Text style={styles.serverRow} numberOfLines={1} ellipsizeMode="middle">{`${server.host}:${server.port || server.sslPort}`}</Text>
<TouchableOpacity accessibilityRole="button" style={styles.selectButton} onPress={() => this.selectServer(server)}>
<BlueText>{loc.settings.electrum_select}</BlueText>
</TouchableOpacity>
</View>
);
});
return (
<>
<BlueCard>
<BlueText style={styles.status}>{loc.settings.electrum_status}</BlueText>
<View style={styles.connectWrap}>
<View style={[styles.container, this.state.config.connected === 1 ? styles.containerConnected : styles.containerDisconnected]}>
<BlueText style={this.state.config.connected === 1 ? styles.textConnected : styles.textDisconnected}>
{this.state.config.connected === 1 ? loc.settings.electrum_connected : loc.settings.electrum_connected_not}
</BlueText>
</View>
</View>
<BlueSpacing20 />
<BlueText style={styles.hostname} onPress={this.checkServer}>
{this.state.config.host}:{this.state.config.port}
</BlueText>
</BlueCard>
<BlueCard>
<View style={styles.inputWrap}>
<TextInput
placeholder={loc.formatString(loc.settings.electrum_host, { example: '10.20.30.40' })}
value={this.state.host}
onChangeText={text => {
const host = text.trim();
this.setState({ host }, () => {
if (host.endsWith('.onion')) {
this.useSSLPortToggled(false);
}
});
}}
numberOfLines={1}
style={styles.inputText}
editable={!this.state.isLoading}
placeholderTextColor="#81868e"
autoCorrect={false}
autoCapitalize="none"
underlineColorAndroid="transparent"
inputAccessoryViewID={DoneAndDismissKeyboardInputAccessoryViewID}
testID="HostInput"
onFocus={() => this.setState({ isAndroidAddressKeyboardVisible: true })}
onBlur={() => this.setState({ isAndroidAddressKeyboardVisible: false })}
/>
</View>
<BlueSpacing20 />
<View style={styles.portWrap}>
<View style={styles.inputWrap}>
<TextInput
placeholder={loc.formatString(loc.settings.electrum_port, { example: '50001' })}
value={this.state.sslPort?.trim() === '' || this.state.sslPort === null ? this.state.port : this.state.sslPort}
onChangeText={text =>
this.setState(prevState => {
if (prevState.sslPort?.trim() === '') {
return { port: text.trim(), sslPort: '' };
} else {
return { port: '', sslPort: text.trim() };
}
})
}
numberOfLines={1}
style={styles.inputText}
editable={!this.state.isLoading}
placeholderTextColor="#81868e"
underlineColorAndroid="transparent"
autoCorrect={false}
autoCapitalize="none"
keyboardType="number-pad"
inputAccessoryViewID={DismissKeyboardInputAccessoryViewID}
testID="PortInput"
onFocus={() => this.setState({ isAndroidNumericKeyboardFocused: true })}
onBlur={() => this.setState({ isAndroidNumericKeyboardFocused: false })}
/>
</View>
<BlueText style={styles.usePort}>{loc.settings.use_ssl}</BlueText>
<Switch
testID="SSLPortInput"
value={this.state.sslPort?.trim() > 0}
onValueChange={this.useSSLPortToggled}
disabled={this.state.host?.endsWith('.onion') ?? false}
/>
</View>
<BlueSpacing20 />
<View style={styles.serverAddTitle}>
<BlueText style={styles.explain}>{loc.settings.electrum_settings_explain}</BlueText>
<TouchableOpacity accessibilityRole="button" testID="ResetToDefault" onPress={() => this.resetToDefault()}>
<BlueText>{loc.settings.electrum_reset}</BlueText>
</TouchableOpacity>
</View>
<BlueSpacing20 />
{this.state.isLoading ? <BlueLoading /> : <Button testID="Save" onPress={this.save} title={loc.settings.save} />}
<BlueSpacing20 />
<BlueButtonLink title={loc.wallets.import_scan_qr} onPress={this.importScan} />
<BlueSpacing20 />
</BlueCard>
{Platform.select({
ios: <DismissKeyboardInputAccessory />,
android: this.state.isAndroidNumericKeyboardFocused && <DismissKeyboardInputAccessory />,
})}
{Platform.select({
ios: (
<DoneAndDismissKeyboardInputAccessory
onClearTapped={() => this.setState({ host: '' })}
onPasteTapped={text => {
this.setState({ host: text });
Keyboard.dismiss();
}}
/>
),
android: this.state.isAndroidAddressKeyboardVisible && (
<DoneAndDismissKeyboardInputAccessory
onClearTapped={() => {
this.setState({ host: '' });
Keyboard.dismiss();
}}
onPasteTapped={text => {
this.setState({ host: text });
Keyboard.dismiss();
}}
/>
),
})}
{serverHistoryItems.length > 0 && !this.state.isLoading && (
<BlueCard>
<View style={styles.serverHistoryTitle}>
<BlueText style={styles.explain}>{loc.settings.electrum_history}</BlueText>
<TouchableOpacity accessibilityRole="button" onPress={() => this.clearHistoryAlert()}>
<BlueText>{loc.settings.electrum_clear}</BlueText>
</TouchableOpacity>
</View>
{serverHistoryItems}
</BlueCard>
)}
</>
);
};
render() {
return (
<ScrollView
keyboardShouldPersistTaps="always"
automaticallyAdjustContentInsets
contentInsetAdjustmentBehavior="automatic"
automaticallyAdjustKeyboardInsets
>
<ListItem
Component={Pressable}
title={loc.settings.electrum_offline_mode}
switch={{
onValueChange: this.onElectrumConnectionEnabledSwitchValueChanged,
value: this.state.isOfflineMode,
testID: 'ElectrumConnectionEnabledSwitch',
}}
subtitle={loc.settings.electrum_offline_description}
/>
{!this.state.isOfflineMode && this.renderElectrumSettings()}
</ScrollView>
);
}
}
ElectrumSettings.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
goBack: PropTypes.func,
}),
route: PropTypes.shape({
name: PropTypes.string,
params: PropTypes.shape({
server: PropTypes.string,
}),
}),
};
const styles = StyleSheet.create({
status: {
textAlign: 'center',
color: BlueCurrentTheme.colors.feeText,
marginBottom: 4,
},
connectWrap: {
width: 'auto',
height: 34,
flexWrap: 'wrap',
justifyContent: 'center',
flexDirection: 'row',
},
container: {
paddingTop: 6,
paddingBottom: 6,
paddingLeft: 16,
paddingRight: 16,
borderRadius: 20,
},
containerConnected: {
backgroundColor: BlueCurrentTheme.colors.feeLabel,
},
containerDisconnected: {
backgroundColor: BlueCurrentTheme.colors.redBG,
},
textConnected: {
color: BlueCurrentTheme.colors.feeValue,
fontWeight: 'bold',
},
textDisconnected: {
color: BlueCurrentTheme.colors.redText,
fontWeight: 'bold',
},
hostname: {
textAlign: 'center',
color: BlueCurrentTheme.colors.foregroundColor,
},
usePort: {
textAlign: 'center',
color: BlueCurrentTheme.colors.foregroundColor,
marginHorizontal: 8,
},
explain: {
color: BlueCurrentTheme.colors.feeText,
marginBottom: -24,
flexShrink: 1,
},
inputWrap: {
flex: 1,
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',
borderRadius: 4,
},
portWrap: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
inputText: {
flex: 1,
marginHorizontal: 8,
minHeight: 36,
color: '#81868e',
height: 36,
},
serverAddTitle: {
flexDirection: 'row',
justifyContent: 'space-between',
marginVertical: 16,
},
serverHistoryTitle: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 10,
},
serverHistoryItem: {
flexDirection: 'row',
paddingVertical: 20,
borderBottomColor: BlueCurrentTheme.colors.formBorder,
borderBottomWidth: 0.5,
flexWrap: 'nowrap',
},
serverRow: {
flexGrow: 2,
maxWidth: '80%',
color: BlueCurrentTheme.colors.foregroundColor,
},
selectButton: {
flexGrow: 1,
marginLeft: 16,
alignItems: 'flex-end',
},
});

View File

@ -488,12 +488,14 @@ const WalletDetails: React.FC = () => {
onValueChange={async (value: boolean) => {
if (wallet.setHideTransactionsInWalletsList) {
wallet.setHideTransactionsInWalletsList(!value);
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
setHideTransactionsInWalletsList(!wallet.getHideTransactionsInWalletsList());
}
try {
await saveToDisk();
} catch (error: any) {
console.log(error.message);
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
console.error(error.message);
}
}}
/>
@ -517,11 +519,13 @@ const WalletDetails: React.FC = () => {
setIsBIP47Enabled(value);
if (wallet.switchBIP47) {
wallet.switchBIP47(value);
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
}
try {
await saveToDisk();
} catch (error: any) {
console.log(error.message);
} catch (error: unknown) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
console.error((error as Error).message);
}
}}
testID="BIP47Switch"
@ -543,11 +547,13 @@ const WalletDetails: React.FC = () => {
setWalletUseWithHardwareWallet(value);
if (wallet.setUseWithHardwareWalletEnabled) {
wallet.setUseWithHardwareWalletEnabled(value);
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
}
try {
await saveToDisk();
} catch (error: any) {
console.log(error.message);
} catch (error: unknown) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
console.error((error as Error).message);
}
}}
/>

View File

@ -85,14 +85,20 @@ describe('BlueWallet UI Tests - no wallets', () => {
// network -> electrum server
// change electrum server to electrum.blockstream.info and revert it back
await element(by.id('ElectrumSettings')).tap();
await element(by.id('ElectrumSettingsScrollView')).swipe('up', 'fast', 1); // in case emu screen is small and it doesnt fit
await element(by.id('HostInput')).replaceText('electrum.blockstream.info\n');
await element(by.id('PortInput')).replaceText('50001\n');
await element(by.id('ElectrumSettingsScrollView')).swipe('up', 'fast', 1); // in case emu screen is small and it doesnt fit
await element(by.id('Save')).tap();
await sup('OK');
await element(by.text('OK')).tap();
await element(by.id('ResetToDefault')).tap();
await element(by.id('HeaderMenuButton')).tap();
await element(by.text('Reset to default')).tap();
await sup('OK');
await element(by.text('OK')).tap();
await sup('OK');
await element(by.text('OK')).tap();
await element(by.id('ElectrumSettingsScrollView')).swipe('up', 'fast', 1); // in case emu screen is small and it doesnt fit
await expect(element(by.id('HostInput'))).toHaveText('');
await expect(element(by.id('PortInput'))).toHaveText('');
await expect(element(by.id('SSLPortInput'))).toHaveToggleValue(false);

View File

@ -164,7 +164,7 @@ jest.mock('react-native-ios-context-menu', () => {
});
jest.mock('rn-qr-generator', () => ({
detect: jest.fn((uri) => {
detect: jest.fn(uri => {
if (uri === 'invalid-image') {
return Promise.reject(new Error('Failed to decode QR code'));
}

View File

@ -20,10 +20,15 @@ const keys = {
SaveChanges: 'saveChanges',
ClearClipboard: 'clearClipboard',
PaymentsCode: 'paymentsCode',
ResetToDefault: 'resetToDefault',
ClearHistory: 'clearHistory',
ScanQR: 'scan_qr',
RemoveAllRecipients: 'RemoveAllRecipients',
AddRecipient: 'AddRecipient',
RemoveRecipient: 'RemoveRecipient',
PasteFromClipboard: 'pasteFromClipboard',
ChoosePhoto: 'choosePhoto',
ImportFile: 'importFile',
};
const icons = {
@ -75,10 +80,16 @@ const icons = {
PaymentsCode: {
iconValue: 'qrcode',
},
ClearHistory: {
iconValue: 'trash',
},
RemoveAllRecipients: { iconValue: 'person.2.slash' },
AddRecipient: { iconValue: 'person.badge.plus' },
RemoveRecipient: { iconValue: 'person.badge.minus' },
PasteFromClipboard: { iconValue: 'document.on.clipboard' },
ScanQR: { iconValue: 'qrcode.viewfinder' },
ChoosePhoto: { iconValue: 'photo.on.rectangle' },
ImportFile: { iconValue: 'doc.badge.plus' },
};
export const CommonToolTipActions = {
@ -141,16 +152,19 @@ export const CommonToolTipActions = {
id: keys.ViewInFiat,
text: loc.total_balance_view.view_in_fiat,
icon: icons.ViewInFiat,
hidden: false,
},
ViewInSats: {
id: keys.ViewInSats,
text: loc.total_balance_view.view_in_sats,
icon: icons.ViewInBitcoin,
hidden: false,
},
ViewInBitcoin: {
id: keys.ViewInBitcoin,
text: loc.total_balance_view.view_in_bitcoin,
icon: icons.ViewInBitcoin,
hidden: false,
},
Entropy: {
id: keys.Entropy,
@ -191,9 +205,33 @@ export const CommonToolTipActions = {
icon: icons.PaymentsCode,
menuState: false,
},
ResetToDefault: {
id: keys.ResetToDefault,
text: loc.settings.electrum_reset,
},
ClearHistory: {
id: keys.ClearHistory,
text: loc.settings.electrum_clear,
icon: icons.ClearHistory,
},
PasteFromClipboard: {
id: keys.PasteFromClipboard,
text: loc.transactions.details_copy_amount,
icon: icons.PasteFromClipboard,
},
ScanQR: {
id: keys.ScanQR,
text: loc.send.details_scan,
icon: icons.ScanQR,
},
ChoosePhoto: {
id: keys.ChoosePhoto,
text: loc._.pick_image,
icon: icons.ChoosePhoto,
},
ImportFile: {
id: keys.ImportFile,
text: loc.wallets.import_file,
icon: icons.ImportFile,
},
};