mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
Merge branch 'master' into clip
This commit is contained in:
commit
f8fd488158
@ -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;
|
||||
|
176
components/AddressInputScanButton.tsx
Normal file
176
components/AddressInputScanButton.tsx
Normal 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,
|
||||
},
|
||||
});
|
@ -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>}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
105
ios/BlueWalletWatch/en_US.lproj/Interface.strings
Normal file
105
ios/BlueWalletWatch/en_US.lproj/Interface.strings
Normal 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
62
ios/Localizable.xcstrings
Normal 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"
|
||||
}
|
@ -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
|
||||
|
15
loc/en.json
15
loc/en.json
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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 = () => (
|
||||
|
@ -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'));
|
||||
|
@ -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 */;
|
||||
|
@ -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"
|
||||
|
@ -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
69
package-lock.json
generated
@ -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"
|
||||
|
16
package.json
16
package.json
@ -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",
|
||||
|
110
screen/lnd/LNDViewAdditionalInvoiceInformation.tsx
Normal file
110
screen/lnd/LNDViewAdditionalInvoiceInformation.tsx
Normal 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;
|
@ -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;
|
@ -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)} />
|
||||
),
|
||||
})}
|
||||
|
@ -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',
|
||||
|
536
screen/settings/ElectrumSettings.tsx
Normal file
536
screen/settings/ElectrumSettings.tsx
Normal 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;
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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',
|
||||
},
|
||||
});
|
@ -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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -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);
|
||||
|
@ -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'));
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user