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
f08eacac0b
@ -34,44 +34,6 @@ async function setPreferredCurrency(item: FiatUnitType): Promise<void> {
|
||||
await DefaultPreference.set(PREFERRED_CURRENCY_LOCALE_STORAGE_KEY, item.locale.replace('-', '_'));
|
||||
}
|
||||
|
||||
async function getPreferredCurrency(): Promise<FiatUnitType> {
|
||||
const preferredCurrency = await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY);
|
||||
|
||||
if (preferredCurrency) {
|
||||
const parsedPreferredCurrency = JSON.parse(preferredCurrency);
|
||||
preferredFiatCurrency = FiatUnit[parsedPreferredCurrency.endPointKey];
|
||||
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
await DefaultPreference.set(PREFERRED_CURRENCY_STORAGE_KEY, preferredFiatCurrency.endPointKey);
|
||||
await DefaultPreference.set(PREFERRED_CURRENCY_LOCALE_STORAGE_KEY, preferredFiatCurrency.locale.replace('-', '_'));
|
||||
return preferredFiatCurrency;
|
||||
}
|
||||
return FiatUnit.USD;
|
||||
}
|
||||
|
||||
async function _restoreSavedExchangeRatesFromStorage(): Promise<void> {
|
||||
try {
|
||||
const rates = await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY);
|
||||
exchangeRates = rates ? JSON.parse(rates) : { LAST_UPDATED_ERROR: false };
|
||||
} catch (_) {
|
||||
exchangeRates = { LAST_UPDATED_ERROR: false };
|
||||
}
|
||||
}
|
||||
|
||||
async function _restoreSavedPreferredFiatCurrencyFromStorage(): Promise<void> {
|
||||
try {
|
||||
const storedCurrency = await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY);
|
||||
if (!storedCurrency) throw new Error('No Preferred Fiat selected');
|
||||
preferredFiatCurrency = JSON.parse(storedCurrency);
|
||||
if (!FiatUnit[preferredFiatCurrency.endPointKey]) {
|
||||
throw new Error('Invalid Fiat Unit');
|
||||
}
|
||||
} catch (_) {
|
||||
const deviceCurrencies = RNLocalize.getCurrencies();
|
||||
preferredFiatCurrency = deviceCurrencies[0] && FiatUnit[deviceCurrencies[0]] ? FiatUnit[deviceCurrencies[0]] : FiatUnit.USD;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateExchangeRate(): Promise<void> {
|
||||
if (skipUpdateExchangeRate) return;
|
||||
if (Date.now() - lastTimeUpdateExchangeRateWasCalled <= 10000) {
|
||||
@ -92,19 +54,140 @@ async function updateExchangeRate(): Promise<void> {
|
||||
exchangeRates[LAST_UPDATED] = Date.now();
|
||||
exchangeRates[BTC_PREFIX + preferredFiatCurrency.endPointKey] = rate;
|
||||
exchangeRates.LAST_UPDATED_ERROR = false;
|
||||
await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(exchangeRates));
|
||||
|
||||
try {
|
||||
const exchangeRatesString = JSON.stringify(exchangeRates);
|
||||
await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, exchangeRatesString);
|
||||
} catch (error) {
|
||||
await AsyncStorage.removeItem(EXCHANGE_RATES_STORAGE_KEY);
|
||||
exchangeRates = { LAST_UPDATED_ERROR: false };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error encountered when attempting to update exchange rate...', error);
|
||||
const rate = JSON.parse((await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)) || '{}');
|
||||
rate.LAST_UPDATED_ERROR = true;
|
||||
exchangeRates.LAST_UPDATED_ERROR = true;
|
||||
await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(rate));
|
||||
try {
|
||||
const ratesString = await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY);
|
||||
let rate;
|
||||
if (ratesString) {
|
||||
try {
|
||||
rate = JSON.parse(ratesString);
|
||||
} catch (parseError) {
|
||||
await AsyncStorage.removeItem(EXCHANGE_RATES_STORAGE_KEY);
|
||||
rate = {};
|
||||
}
|
||||
} else {
|
||||
rate = {};
|
||||
}
|
||||
rate.LAST_UPDATED_ERROR = true;
|
||||
exchangeRates.LAST_UPDATED_ERROR = true;
|
||||
await AsyncStorage.setItem(EXCHANGE_RATES_STORAGE_KEY, JSON.stringify(rate));
|
||||
} catch (storageError) {}
|
||||
}
|
||||
}
|
||||
|
||||
async function getPreferredCurrency(): Promise<FiatUnitType> {
|
||||
const preferredCurrency = await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY);
|
||||
|
||||
if (preferredCurrency) {
|
||||
let parsedPreferredCurrency;
|
||||
try {
|
||||
parsedPreferredCurrency = JSON.parse(preferredCurrency);
|
||||
if (!FiatUnit[parsedPreferredCurrency.endPointKey]) {
|
||||
throw new Error('Invalid Fiat Unit');
|
||||
}
|
||||
preferredFiatCurrency = FiatUnit[parsedPreferredCurrency.endPointKey];
|
||||
} catch (error) {
|
||||
await AsyncStorage.removeItem(PREFERRED_CURRENCY_STORAGE_KEY);
|
||||
|
||||
const deviceCurrencies = RNLocalize.getCurrencies();
|
||||
if (deviceCurrencies[0] && FiatUnit[deviceCurrencies[0]]) {
|
||||
preferredFiatCurrency = FiatUnit[deviceCurrencies[0]];
|
||||
} else {
|
||||
preferredFiatCurrency = FiatUnit.USD;
|
||||
}
|
||||
}
|
||||
|
||||
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
|
||||
await DefaultPreference.set(PREFERRED_CURRENCY_STORAGE_KEY, preferredFiatCurrency.endPointKey);
|
||||
await DefaultPreference.set(PREFERRED_CURRENCY_LOCALE_STORAGE_KEY, preferredFiatCurrency.locale.replace('-', '_'));
|
||||
return preferredFiatCurrency;
|
||||
}
|
||||
|
||||
const deviceCurrencies = RNLocalize.getCurrencies();
|
||||
if (deviceCurrencies[0] && FiatUnit[deviceCurrencies[0]]) {
|
||||
preferredFiatCurrency = FiatUnit[deviceCurrencies[0]];
|
||||
} else {
|
||||
preferredFiatCurrency = FiatUnit.USD;
|
||||
}
|
||||
|
||||
return preferredFiatCurrency;
|
||||
}
|
||||
|
||||
async function _restoreSavedExchangeRatesFromStorage(): Promise<void> {
|
||||
try {
|
||||
const rates = await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY);
|
||||
if (rates) {
|
||||
try {
|
||||
exchangeRates = JSON.parse(rates);
|
||||
} catch (error) {
|
||||
await AsyncStorage.removeItem(EXCHANGE_RATES_STORAGE_KEY);
|
||||
exchangeRates = { LAST_UPDATED_ERROR: false };
|
||||
await updateExchangeRate();
|
||||
}
|
||||
} else {
|
||||
exchangeRates = { LAST_UPDATED_ERROR: false };
|
||||
}
|
||||
} catch (error) {
|
||||
exchangeRates = { LAST_UPDATED_ERROR: false };
|
||||
await updateExchangeRate();
|
||||
}
|
||||
}
|
||||
|
||||
async function _restoreSavedPreferredFiatCurrencyFromStorage(): Promise<void> {
|
||||
try {
|
||||
const storedCurrency = await AsyncStorage.getItem(PREFERRED_CURRENCY_STORAGE_KEY);
|
||||
if (!storedCurrency) throw new Error('No Preferred Fiat selected');
|
||||
|
||||
let parsedCurrency;
|
||||
try {
|
||||
parsedCurrency = JSON.parse(storedCurrency);
|
||||
if (!FiatUnit[parsedCurrency.endPointKey]) {
|
||||
throw new Error('Invalid Fiat Unit');
|
||||
}
|
||||
preferredFiatCurrency = FiatUnit[parsedCurrency.endPointKey];
|
||||
} catch (error) {
|
||||
await AsyncStorage.removeItem(PREFERRED_CURRENCY_STORAGE_KEY);
|
||||
|
||||
const deviceCurrencies = RNLocalize.getCurrencies();
|
||||
if (deviceCurrencies[0] && FiatUnit[deviceCurrencies[0]]) {
|
||||
preferredFiatCurrency = FiatUnit[deviceCurrencies[0]];
|
||||
} else {
|
||||
preferredFiatCurrency = FiatUnit.USD;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const deviceCurrencies = RNLocalize.getCurrencies();
|
||||
if (deviceCurrencies[0] && FiatUnit[deviceCurrencies[0]]) {
|
||||
preferredFiatCurrency = FiatUnit[deviceCurrencies[0]];
|
||||
} else {
|
||||
preferredFiatCurrency = FiatUnit.USD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function isRateOutdated(): Promise<boolean> {
|
||||
try {
|
||||
const rate = JSON.parse((await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)) || '{}');
|
||||
const rateString = await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY);
|
||||
let rate;
|
||||
if (rateString) {
|
||||
try {
|
||||
rate = JSON.parse(rateString);
|
||||
} catch (parseError) {
|
||||
await AsyncStorage.removeItem(EXCHANGE_RATES_STORAGE_KEY);
|
||||
rate = {};
|
||||
await updateExchangeRate();
|
||||
}
|
||||
} else {
|
||||
rate = {};
|
||||
}
|
||||
return rate.LAST_UPDATED_ERROR || Date.now() - (rate[LAST_UPDATED] || 0) >= 31 * 60 * 1000;
|
||||
} catch {
|
||||
return true;
|
||||
@ -169,18 +252,37 @@ function BTCToLocalCurrency(bitcoin: BigNumber.Value): string {
|
||||
}
|
||||
|
||||
async function mostRecentFetchedRate(): Promise<CurrencyRate> {
|
||||
const currencyInformation = JSON.parse((await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY)) || '{}');
|
||||
try {
|
||||
const currencyInformationString = await AsyncStorage.getItem(EXCHANGE_RATES_STORAGE_KEY);
|
||||
let currencyInformation;
|
||||
if (currencyInformationString) {
|
||||
try {
|
||||
currencyInformation = JSON.parse(currencyInformationString);
|
||||
} catch (parseError) {
|
||||
await AsyncStorage.removeItem(EXCHANGE_RATES_STORAGE_KEY);
|
||||
currencyInformation = {};
|
||||
await updateExchangeRate();
|
||||
}
|
||||
} else {
|
||||
currencyInformation = {};
|
||||
}
|
||||
|
||||
const formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, {
|
||||
style: 'currency',
|
||||
currency: preferredFiatCurrency.endPointKey,
|
||||
});
|
||||
const formatter = new Intl.NumberFormat(preferredFiatCurrency.locale, {
|
||||
style: 'currency',
|
||||
currency: preferredFiatCurrency.endPointKey,
|
||||
});
|
||||
|
||||
const rate = currencyInformation[BTC_PREFIX + preferredFiatCurrency.endPointKey];
|
||||
return {
|
||||
LastUpdated: currencyInformation[LAST_UPDATED],
|
||||
Rate: rate ? formatter.format(rate) : '...',
|
||||
};
|
||||
const rate = currencyInformation[BTC_PREFIX + preferredFiatCurrency.endPointKey];
|
||||
return {
|
||||
LastUpdated: currencyInformation[LAST_UPDATED],
|
||||
Rate: rate ? formatter.format(rate) : '...',
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
LastUpdated: null,
|
||||
Rate: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function satoshiToBTC(satoshi: number): string {
|
||||
|
@ -1,15 +1,10 @@
|
||||
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, { useCallback } 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';
|
||||
import DeeplinkSchemaMatch from '../class/deeplink-schema-match';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback';
|
||||
|
||||
interface AddressInputProps {
|
||||
isLoading?: boolean;
|
||||
@ -22,6 +17,8 @@ interface AddressInputProps {
|
||||
editable?: boolean;
|
||||
inputAccessoryViewID?: string;
|
||||
onBlur?: () => void;
|
||||
onFocus?: () => void;
|
||||
testID?: string;
|
||||
keyboardType?:
|
||||
| 'default'
|
||||
| 'numeric'
|
||||
@ -41,6 +38,7 @@ interface AddressInputProps {
|
||||
const AddressInput = ({
|
||||
isLoading = false,
|
||||
address = '',
|
||||
testID = 'AddressInput',
|
||||
placeholder = loc.send.details_address,
|
||||
onChangeText,
|
||||
onBarScanned,
|
||||
@ -49,6 +47,7 @@ const AddressInput = ({
|
||||
editable = true,
|
||||
inputAccessoryViewID,
|
||||
onBlur = () => {},
|
||||
onFocus = () => {},
|
||||
keyboardType = 'default',
|
||||
}: AddressInputProps) => {
|
||||
const { colors } = useTheme();
|
||||
@ -58,142 +57,56 @@ const AddressInput = ({
|
||||
borderBottomColor: colors.formBorder,
|
||||
backgroundColor: colors.inputBackgroundColor,
|
||||
},
|
||||
scan: {
|
||||
backgroundColor: colors.scanLabel,
|
||||
},
|
||||
scanText: {
|
||||
color: colors.inverseForegroundColor,
|
||||
input: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
});
|
||||
|
||||
const validateAddressWithFeedback = useCallback((value: string) => {
|
||||
const isBitcoinAddress = DeeplinkSchemaMatch.isBitcoinAddress(value);
|
||||
const isLightningInvoice = DeeplinkSchemaMatch.isLightningInvoice(value);
|
||||
const isValid = isBitcoinAddress || isLightningInvoice;
|
||||
|
||||
triggerHapticFeedback(isValid ? HapticFeedbackTypes.NotificationSuccess : HapticFeedbackTypes.NotificationError);
|
||||
return {
|
||||
isValid,
|
||||
type: isBitcoinAddress ? 'bitcoin' : isLightningInvoice ? 'lightning' : 'invalid',
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onBlurEditing = () => {
|
||||
validateAddressWithFeedback(address);
|
||||
onBlur();
|
||||
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,75 +119,16 @@ const styles = StyleSheet.create({
|
||||
borderBottomWidth: 0.5,
|
||||
minHeight: 44,
|
||||
height: 44,
|
||||
marginHorizontal: 20,
|
||||
alignItems: 'center',
|
||||
marginVertical: 8,
|
||||
marginHorizontal: 18,
|
||||
borderRadius: 4,
|
||||
},
|
||||
input: {
|
||||
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,
|
||||
minHeight: 33,
|
||||
},
|
||||
});
|
||||
|
||||
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,19 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Pressable, Platform } from 'react-native';
|
||||
import ToolTipMenu from './TooltipMenu';
|
||||
import { useTheme } from './themes';
|
||||
import { Icon } from '@rneui/themed';
|
||||
import { Platform } from 'react-native';
|
||||
import { Action } from './types';
|
||||
|
||||
interface HeaderMenuButtonProps {
|
||||
onPressMenuItem: (id: string) => void;
|
||||
actions: Action[];
|
||||
actions?: Action[] | Action[][];
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const HeaderMenuButton: React.FC<HeaderMenuButtonProps> = ({ onPressMenuItem, actions, disabled }) => {
|
||||
const { colors } = useTheme();
|
||||
const styleProps = Platform.OS === 'android' ? { iconStyle: { transform: [{ rotate: '90deg' }] } } : {};
|
||||
|
||||
if (!actions || actions.length === 0) {
|
||||
return (
|
||||
<Pressable
|
||||
testID="HeaderMenuButton"
|
||||
disabled={disabled}
|
||||
android_ripple={{ color: colors.lightButton }}
|
||||
style={({ pressed }) => [{ opacity: pressed ? 0.5 : 1 }]}
|
||||
>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} {...styleProps} />
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
const menuActions = Array.isArray(actions[0]) ? (actions as Action[][]) : (actions as Action[]);
|
||||
|
||||
return (
|
||||
<ToolTipMenu
|
||||
testID="HeaderMenuButton"
|
||||
@ -21,7 +37,7 @@ const HeaderMenuButton: React.FC<HeaderMenuButtonProps> = ({ onPressMenuItem, ac
|
||||
isButton
|
||||
isMenuPrimaryAction
|
||||
onPressMenuItem={onPressMenuItem}
|
||||
actions={actions}
|
||||
actions={menuActions}
|
||||
>
|
||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} {...styleProps} />
|
||||
</ToolTipMenu>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
import { ListItem } from '@rneui/themed';
|
||||
import Share from 'react-native-share';
|
||||
@ -12,15 +11,15 @@ import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import presentAlert from '../Alert';
|
||||
import QRCodeComponent from '../QRCodeComponent';
|
||||
import { useTheme } from '../themes';
|
||||
import { Action } from '../types';
|
||||
import { AddressTypeBadge } from './AddressTypeBadge';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import ToolTipMenu from '../TooltipMenu';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
|
||||
interface AddressItemProps {
|
||||
// todo: fix `any` after addresses.js is converted to the church of holy typescript
|
||||
item: any;
|
||||
balanceUnit: BitcoinUnit;
|
||||
walletID: string;
|
||||
@ -55,9 +54,9 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
||||
},
|
||||
});
|
||||
|
||||
const { navigate } = useNavigation<NavigationProps>();
|
||||
const { navigate } = useExtendedNavigation<NavigationProps>();
|
||||
|
||||
const navigateToReceive = () => {
|
||||
const navigateToReceive = useCallback(() => {
|
||||
navigate('ReceiveDetailsRoot', {
|
||||
screen: 'ReceiveDetails',
|
||||
params: {
|
||||
@ -65,9 +64,9 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
||||
address: item.address,
|
||||
},
|
||||
});
|
||||
};
|
||||
}, [navigate, walletID, item.address]);
|
||||
|
||||
const navigateToSignVerify = () => {
|
||||
const navigateToSignVerify = useCallback(() => {
|
||||
navigate('SignVerifyRoot', {
|
||||
screen: 'SignVerify',
|
||||
params: {
|
||||
@ -75,21 +74,36 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
||||
address: item.address,
|
||||
},
|
||||
});
|
||||
};
|
||||
}, [navigate, walletID, item.address]);
|
||||
|
||||
const menuActions = useMemo(() => getAvailableActions({ allowSignVerifyMessage }), [allowSignVerifyMessage]);
|
||||
const menuActions = useMemo(
|
||||
() =>
|
||||
[
|
||||
CommonToolTipActions.CopyTXID,
|
||||
CommonToolTipActions.Share,
|
||||
{
|
||||
...CommonToolTipActions.SignVerify,
|
||||
hidden: !allowSignVerifyMessage,
|
||||
},
|
||||
{
|
||||
...CommonToolTipActions.ExportPrivateKey,
|
||||
hidden: !allowSignVerifyMessage,
|
||||
},
|
||||
].filter(action => !action.hidden),
|
||||
[allowSignVerifyMessage],
|
||||
);
|
||||
|
||||
const balance = formatBalance(item.balance, balanceUnit, true);
|
||||
|
||||
const handleCopyPress = () => {
|
||||
const handleCopyPress = useCallback(() => {
|
||||
Clipboard.setString(item.address);
|
||||
};
|
||||
}, [item.address]);
|
||||
|
||||
const handleSharePress = () => {
|
||||
const handleSharePress = useCallback(() => {
|
||||
Share.open({ message: item.address }).catch(error => console.log(error));
|
||||
};
|
||||
}, [item.address]);
|
||||
|
||||
const handleCopyPrivkeyPress = () => {
|
||||
const handleCopyPrivkeyPress = useCallback(() => {
|
||||
const wallet = wallets.find(w => w.getID() === walletID);
|
||||
if (!wallet) {
|
||||
presentAlert({ message: 'Internal error: cant find wallet' });
|
||||
@ -107,86 +121,60 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
||||
} catch (error: any) {
|
||||
presentAlert({ message: error.message });
|
||||
}
|
||||
};
|
||||
}, [wallets, walletID, item.address]);
|
||||
|
||||
const onToolTipPress = async (id: string) => {
|
||||
if (id === actionKeys.CopyToClipboard) {
|
||||
handleCopyPress();
|
||||
} else if (id === actionKeys.Share) {
|
||||
handleSharePress();
|
||||
} else if (id === actionKeys.SignVerify) {
|
||||
navigateToSignVerify();
|
||||
} else if (id === actionKeys.ExportPrivateKey) {
|
||||
if (await confirm(loc.addresses.sensitive_private_key)) {
|
||||
if (await isBiometricUseCapableAndEnabled()) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
return;
|
||||
const onToolTipPress = useCallback(
|
||||
async (id: string) => {
|
||||
if (id === CommonToolTipActions.CopyTXID.id) {
|
||||
handleCopyPress();
|
||||
} else if (id === CommonToolTipActions.Share.id) {
|
||||
handleSharePress();
|
||||
} else if (id === CommonToolTipActions.SignVerify.id) {
|
||||
navigateToSignVerify();
|
||||
} else if (id === CommonToolTipActions.ExportPrivateKey.id) {
|
||||
if (await confirm(loc.addresses.sensitive_private_key)) {
|
||||
if (await isBiometricUseCapableAndEnabled()) {
|
||||
if (!(await unlockWithBiometrics())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
handleCopyPrivkeyPress();
|
||||
}
|
||||
|
||||
handleCopyPrivkeyPress();
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
[handleCopyPress, handleSharePress, navigateToSignVerify, handleCopyPrivkeyPress, isBiometricUseCapableAndEnabled],
|
||||
);
|
||||
|
||||
const renderPreview = () => {
|
||||
return <QRCodeComponent value={item.address} isMenuAvailable={false} />;
|
||||
};
|
||||
const renderPreview = useCallback(() => <QRCodeComponent value={item.address} isMenuAvailable={false} />, [item.address]);
|
||||
|
||||
const render = () => {
|
||||
return (
|
||||
<ToolTipMenu
|
||||
title={item.address}
|
||||
actions={menuActions}
|
||||
onPressMenuItem={onToolTipPress}
|
||||
renderPreview={renderPreview}
|
||||
onPress={navigateToReceive}
|
||||
isButton
|
||||
>
|
||||
<ListItem key={item.key} containerStyle={stylesHook.container}>
|
||||
<ListItem.Content style={stylesHook.list}>
|
||||
<ListItem.Title style={stylesHook.list} numberOfLines={1} ellipsizeMode="middle">
|
||||
<Text style={[styles.index, stylesHook.index]}>{item.index + 1}</Text>{' '}
|
||||
<Text style={[stylesHook.address, styles.address]}>{item.address}</Text>
|
||||
</ListItem.Title>
|
||||
<View style={styles.subtitle}>
|
||||
<Text style={[stylesHook.list, styles.balance, stylesHook.balance]}>{balance}</Text>
|
||||
</View>
|
||||
</ListItem.Content>
|
||||
<View>
|
||||
<AddressTypeBadge isInternal={item.isInternal} hasTransactions={hasTransactions} />
|
||||
<Text style={[stylesHook.list, styles.balance, stylesHook.balance]}>
|
||||
{loc.addresses.transactions}: {item.transactions}
|
||||
</Text>
|
||||
return (
|
||||
<ToolTipMenu
|
||||
title={item.address}
|
||||
actions={menuActions}
|
||||
onPressMenuItem={onToolTipPress}
|
||||
renderPreview={renderPreview}
|
||||
onPress={navigateToReceive}
|
||||
isButton
|
||||
>
|
||||
<ListItem key={item.key} containerStyle={stylesHook.container}>
|
||||
<ListItem.Content style={stylesHook.list}>
|
||||
<ListItem.Title style={stylesHook.list} numberOfLines={1} ellipsizeMode="middle">
|
||||
<Text style={[styles.index, stylesHook.index]}>{item.index + 1}</Text>{' '}
|
||||
<Text style={[stylesHook.address, styles.address]}>{item.address}</Text>
|
||||
</ListItem.Title>
|
||||
<View style={styles.subtitle}>
|
||||
<Text style={[stylesHook.list, styles.balance, stylesHook.balance]}>{balance}</Text>
|
||||
</View>
|
||||
</ListItem>
|
||||
</ToolTipMenu>
|
||||
);
|
||||
};
|
||||
|
||||
return render();
|
||||
};
|
||||
|
||||
const actionKeys = {
|
||||
Share: 'share',
|
||||
CopyToClipboard: 'copyToClipboard',
|
||||
SignVerify: 'signVerify',
|
||||
ExportPrivateKey: 'exportPrivateKey',
|
||||
};
|
||||
|
||||
const actionIcons = {
|
||||
Signature: {
|
||||
iconValue: 'signature',
|
||||
},
|
||||
Share: {
|
||||
iconValue: 'square.and.arrow.up',
|
||||
},
|
||||
Clipboard: {
|
||||
iconValue: 'doc.on.doc',
|
||||
},
|
||||
ExportPrivateKey: {
|
||||
iconValue: 'key',
|
||||
},
|
||||
</ListItem.Content>
|
||||
<View>
|
||||
<AddressTypeBadge isInternal={item.isInternal} hasTransactions={hasTransactions} />
|
||||
<Text style={[stylesHook.list, styles.balance, stylesHook.balance]}>
|
||||
{loc.addresses.transactions}: {item.transactions}
|
||||
</Text>
|
||||
</View>
|
||||
</ListItem>
|
||||
</ToolTipMenu>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@ -209,37 +197,4 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
const getAvailableActions = ({ allowSignVerifyMessage }: { allowSignVerifyMessage: boolean }): Action[] => {
|
||||
const actions = [
|
||||
{
|
||||
id: actionKeys.CopyToClipboard,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: actionIcons.Clipboard,
|
||||
},
|
||||
{
|
||||
id: actionKeys.Share,
|
||||
text: loc.receive.details_share,
|
||||
icon: actionIcons.Share,
|
||||
},
|
||||
];
|
||||
|
||||
if (allowSignVerifyMessage) {
|
||||
actions.push({
|
||||
id: actionKeys.SignVerify,
|
||||
text: loc.addresses.sign_title,
|
||||
icon: actionIcons.Signature,
|
||||
});
|
||||
}
|
||||
|
||||
if (allowSignVerifyMessage) {
|
||||
actions.push({
|
||||
id: actionKeys.ExportPrivateKey,
|
||||
text: loc.addresses.copy_private_key,
|
||||
icon: actionIcons.ExportPrivateKey,
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
export { AddressItem };
|
||||
|
@ -19,7 +19,7 @@ export interface Action {
|
||||
}
|
||||
|
||||
export interface ToolTipMenuProps {
|
||||
actions: Action[];
|
||||
actions: Action[] | Action[][];
|
||||
children: React.ReactNode;
|
||||
enableAndroidRipple?: boolean;
|
||||
dismissMenu?: () => void;
|
||||
|
@ -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
|
||||
@ -1319,7 +1319,7 @@ PODS:
|
||||
- React
|
||||
- react-native-randombytes (3.6.1):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.11.1):
|
||||
- react-native-safe-area-context (4.12.0):
|
||||
- React-Core
|
||||
- react-native-screen-capture (0.2.3):
|
||||
- React
|
||||
@ -1592,15 +1592,15 @@ PODS:
|
||||
- React-Core
|
||||
- RealmJS (20.0.0):
|
||||
- React
|
||||
- RNCAsyncStorage (1.24.0):
|
||||
- RNCAsyncStorage (2.0.0):
|
||||
- React-Core
|
||||
- RNCClipboard (1.14.2):
|
||||
- RNCClipboard (1.14.3):
|
||||
- React-Core
|
||||
- RNCPushNotificationIOS (1.11.0):
|
||||
- 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
|
||||
@ -1748,7 +1767,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNScreens (3.34.0):
|
||||
- RNScreens (3.35.0):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@ -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
|
||||
@ -2190,7 +2228,7 @@ SPEC CHECKSUMS:
|
||||
react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5
|
||||
react-native-menu: c30eb7a85d7b04d51945f61ea8a8986ed366ac5c
|
||||
react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
|
||||
react-native-safe-area-context: 5141f11858b033636f1788b14f32eaba92cee810
|
||||
react-native-safe-area-context: 142fade490cbebbe428640b8cbdb09daf17e8191
|
||||
react-native-screen-capture: 75db9b051c41fea47fa68665506e9257d4b1dadc
|
||||
react-native-secure-key-store: 910e6df6bc33cb790aba6ee24bc7818df1fe5898
|
||||
react-native-tcp-socket: 8c3e8bef909ab06c557eeb95363fe029391ff09d
|
||||
@ -2222,24 +2260,24 @@ SPEC CHECKSUMS:
|
||||
ReactCommon: 6a952e50c2a4b694731d7682aaa6c79bc156e4ad
|
||||
ReactNativeCameraKit: 9d46a5d7dd544ca64aa9c03c150d2348faf437eb
|
||||
RealmJS: 6946b520bada5568b7e92a5242e138d3a19aa69f
|
||||
RNCAsyncStorage: ec53e44dc3e75b44aa2a9f37618a49c3bc080a7a
|
||||
RNCClipboard: 5e503962f0719ace8f7fdfe9c60282b526305c85
|
||||
RNCAsyncStorage: d35c79ffba52c1013013e16b1fc295aec2feabb6
|
||||
RNCClipboard: 2821ac938ef46f736a8de0c8814845dde2dcbdfb
|
||||
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
|
||||
RNScreens: c7ceced6a8384cb9be5e7a5e88e9e714401fd958
|
||||
RNShare: eaeb5e7dc1618d19db6234da1af91fc60dd6bc0f
|
||||
RNSVG: 8b1a777d54096b8c2a0fd38fc9d5a454332bbb4d
|
||||
RNVectorIcons: 6382277afab3c54658e9d555ee0faa7a37827136
|
||||
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
|
||||
|
@ -290,7 +290,7 @@
|
||||
"details_outputs": "المخرجات",
|
||||
"date": "التاريخ",
|
||||
"details_received": "التاريخ",
|
||||
"details_show_in_block_explorer": "العرض في مستكشف الكتل",
|
||||
"details_view_in_browser": "العرض في مستكشف الكتل",
|
||||
"details_title": "العملية",
|
||||
"details_to": "إلى",
|
||||
"enable_offline_signing": "هذه المحفظة لا يتم استعمالها مع التوقيع دون اتصال. هل ترغب في تمكينه الآن؟",
|
||||
|
@ -213,7 +213,7 @@
|
||||
"details_outputs": "Sortides",
|
||||
"date": "Data",
|
||||
"details_received": "Rebut",
|
||||
"details_show_in_block_explorer": "Mostrar en l'explorador de blocs",
|
||||
"details_view_in_browser": "Mostrar en l'explorador de blocs",
|
||||
"details_title": "Transacció",
|
||||
"details_to": "A",
|
||||
"list_conf": "Conf: {number}",
|
||||
|
@ -344,7 +344,7 @@
|
||||
"details_outputs": "Výstupy",
|
||||
"date": "Datum",
|
||||
"details_received": "Přijato",
|
||||
"details_show_in_block_explorer": "Zobrazit v průzkumníku bloků",
|
||||
"details_view_in_browser": "Zobrazit v průzkumníku bloků",
|
||||
"details_title": "Transakce",
|
||||
"incoming_transaction": "Příchozí transakce",
|
||||
"outgoing_transaction": "Odchozí transakce",
|
||||
|
@ -72,7 +72,7 @@
|
||||
"cpfp_create": "Opret",
|
||||
"details_copy": "Kopier",
|
||||
"details_from": "Fra",
|
||||
"details_show_in_block_explorer": "Vis i block-explorer",
|
||||
"details_view_in_browser": "Vis i block-explorer",
|
||||
"details_title": "Transaktion",
|
||||
"details_to": "Til",
|
||||
"list_title": "transaktioner",
|
||||
|
@ -339,7 +339,7 @@
|
||||
"details_outputs": "Ausgänge",
|
||||
"date": "Datum",
|
||||
"details_received": "Empfangen",
|
||||
"details_show_in_block_explorer": "Im Block-Explorer zeigen",
|
||||
"details_view_in_browser": "Im Block-Explorer zeigen",
|
||||
"details_title": "Transaktion",
|
||||
"incoming_transaction": "Eingehende Transaktion",
|
||||
"outgoing_transaction": "Ausgehende Transaktion",
|
||||
|
@ -223,7 +223,7 @@
|
||||
"details_from": "Εισερχόμενες διευθύνσεις",
|
||||
"date": "Ημερομηνία",
|
||||
"details_received": "Ελήφθη",
|
||||
"details_show_in_block_explorer": "Προβολή στον block explorer",
|
||||
"details_view_in_browser": "Προβολή στον block explorer",
|
||||
"details_title": "Συναλλαγή",
|
||||
"details_to": "Εξερχόμενες διευθύνσεις",
|
||||
"pending": "Σε επεξεργασία",
|
||||
|
19
loc/en.json
19
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",
|
||||
@ -228,7 +228,7 @@
|
||||
"biom_no_passcode": "Your device does not have a passcode or biometrics enabled. In order to proceed, please configure a passcode or biometric in the Settings app.",
|
||||
"biom_remove_decrypt": "All your wallets will be removed and your storage will be decrypted. Are you sure you want to proceed?",
|
||||
"currency": "Currency",
|
||||
"currency_source": "Price is obtained from",
|
||||
"currency_source": "Rate is obtained from",
|
||||
"currency_fetch_error": "There was an error while obtaining the rate for the selected currency.",
|
||||
"default_desc": "When disabled, BlueWallet will immediately open the selected wallet at launch.",
|
||||
"default_info": "Default info",
|
||||
@ -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",
|
||||
@ -345,7 +344,7 @@
|
||||
"details_outputs": "Outputs",
|
||||
"date": "Date",
|
||||
"details_received": "Received",
|
||||
"details_show_in_block_explorer": "View in Block Explorer",
|
||||
"details_view_in_browser": "View in Browser",
|
||||
"details_title": "Transaction",
|
||||
"incoming_transaction": "Incoming Transaction",
|
||||
"outgoing_transaction": "Outgoing Transaction",
|
||||
@ -652,4 +651,4 @@
|
||||
"notif_tx": "Notification transaction",
|
||||
"not_found": "Payment code not found"
|
||||
}
|
||||
}
|
||||
}
|
@ -280,7 +280,7 @@
|
||||
"details_outputs": "Outputs",
|
||||
"date": "Fecha",
|
||||
"details_received": "Recibido",
|
||||
"details_show_in_block_explorer": "Mostrar en explorador de bloques",
|
||||
"details_view_in_browser": "Mostrar en explorador de bloques",
|
||||
"details_title": "Transaccion",
|
||||
"details_to": "Destino",
|
||||
"enable_offline_signing": "Esta billetera no se está usando en conjunción con una firma offline. ¿Quieres activarlo ahora? ",
|
||||
|
@ -344,7 +344,7 @@
|
||||
"details_outputs": "Salidas",
|
||||
"date": "Fecha",
|
||||
"details_received": "Recibido",
|
||||
"details_show_in_block_explorer": "Ver en el Explorador de Bloques",
|
||||
"details_view_in_browser": "Ver en el Explorador de Bloques",
|
||||
"details_title": "Transacción",
|
||||
"incoming_transaction": "Transacción entrante",
|
||||
"outgoing_transaction": "Transacción saliente",
|
||||
|
@ -293,7 +293,7 @@
|
||||
"details_outputs": "خروجیها",
|
||||
"date": "تاریخ",
|
||||
"details_received": "دریافتشده",
|
||||
"details_show_in_block_explorer": "مشاهده در مرورگر بلاک",
|
||||
"details_view_in_browser": "مشاهده در مرورگر بلاک",
|
||||
"details_title": "تراکنش",
|
||||
"details_to": "خروجی",
|
||||
"enable_offline_signing": "این کیف پول در کنار امضای آفلاین استفاده نمیشود. آیا مایل به فعالکردن این امکان هستید؟",
|
||||
|
@ -309,7 +309,7 @@
|
||||
"details_outputs": "Ulostulot",
|
||||
"date": "Päivämäärä",
|
||||
"details_received": "Vastaanotettu",
|
||||
"details_show_in_block_explorer": "Näytä lohkoketjuselaimessa",
|
||||
"details_view_in_browser": "Näytä lohkoketjuselaimessa",
|
||||
"details_title": "Siirtotapahtuma",
|
||||
"details_to": "Ulostulo",
|
||||
"enable_offline_signing": "Tätä lompakkoa ei käytetä offline-allekirjoituksen yhteydessä. Haluatko ottaa sen käyttöön nyt? ",
|
||||
|
@ -294,7 +294,7 @@
|
||||
"details_inputs": "Inputs",
|
||||
"details_outputs": "Outputs",
|
||||
"details_received": "Reçu",
|
||||
"details_show_in_block_explorer": "Afficher dans le \"block explorer\"",
|
||||
"details_view_in_browser": "Afficher dans le \"block explorer\"",
|
||||
"details_title": "Transaction",
|
||||
"details_to": "À",
|
||||
"enable_offline_signing": "Ce portefeuille n'est pas utilisé en conjonction avec une signature hors ligne. Voulez-vous l'activer maintenant ? ",
|
||||
|
@ -323,7 +323,7 @@
|
||||
"details_outputs": "פלטים",
|
||||
"date": "תאריך",
|
||||
"details_received": "התקבל",
|
||||
"details_show_in_block_explorer": "צפייה בסייר בלוקים",
|
||||
"details_view_in_browser": "צפייה בסייר בלוקים",
|
||||
"details_title": "פעולה",
|
||||
"incoming_transaction": "פעולה נכנסת",
|
||||
"outgoing_transaction": "פעולה יוצאת",
|
||||
|
@ -80,7 +80,7 @@
|
||||
"cpfp_create": "Stvori",
|
||||
"details_copy": "Kopiraj",
|
||||
"details_from": "Od",
|
||||
"details_show_in_block_explorer": "Prikaži u blok eksploreru",
|
||||
"details_view_in_browser": "Prikaži u blok eksploreru",
|
||||
"details_title": "Transakcija",
|
||||
"details_to": "Za",
|
||||
"list_title": "transakcije",
|
||||
|
@ -288,7 +288,7 @@
|
||||
"details_inputs": "Bejövő utalások",
|
||||
"details_outputs": "Kimenő utalások",
|
||||
"details_received": "Fogadott",
|
||||
"details_show_in_block_explorer": "Mutasd a block explorerben",
|
||||
"details_view_in_browser": "Mutasd a block explorerben",
|
||||
"details_title": "Tranzakció",
|
||||
"details_to": "Kimenő utalás",
|
||||
"enable_offline_signing": "Ezt a pénztárcát nem használják offline aláírással. Szeretné most engedélyezni?",
|
||||
|
@ -259,7 +259,7 @@
|
||||
"details_inputs": "Input",
|
||||
"date": "Tanggal",
|
||||
"details_received": "Diterima",
|
||||
"details_show_in_block_explorer": "Tampilkan di block explorer",
|
||||
"details_view_in_browser": "Tampilkan di block explorer",
|
||||
"details_title": "Transaksi",
|
||||
"pending": "tertunda",
|
||||
"pending_with_amount": "Tertunda {amt1} ({amt2})",
|
||||
|
@ -288,7 +288,7 @@
|
||||
"details_outputs": "Output",
|
||||
"date": "Data",
|
||||
"details_received": "Ricevuto",
|
||||
"details_show_in_block_explorer": "Mostra sul block explorer",
|
||||
"details_view_in_browser": "Mostra sul block explorer",
|
||||
"details_title": "Transazione",
|
||||
"details_to": "A",
|
||||
"enable_offline_signing": "Questo wallet non sta venendo usato con la firma offline. Desideri attivarla adesso?",
|
||||
|
@ -339,7 +339,7 @@
|
||||
"details_outputs": "アウトプット",
|
||||
"date": "日付",
|
||||
"details_received": "受取り済",
|
||||
"details_show_in_block_explorer": "Block Explorer で表示",
|
||||
"details_view_in_browser": "Block Explorer で表示",
|
||||
"details_title": "取引",
|
||||
"incoming_transaction": "受取りトランザクション",
|
||||
"outgoing_transaction": "支払いトランザクション",
|
||||
|
@ -273,7 +273,7 @@
|
||||
"details_inputs": "입력",
|
||||
"details_outputs": "출력",
|
||||
"details_received": "받기 완료",
|
||||
"details_show_in_block_explorer": "블록 익스플로러에서 보기",
|
||||
"details_view_in_browser": "블록 익스플로러에서 보기",
|
||||
"details_title": "트랜잭션",
|
||||
"details_to": "출력",
|
||||
"enable_offline_signing": "이 지갑은 오프라인 서명과 함께 쓸수 없습니다. 지금 가능하도록 할까요?",
|
||||
|
@ -255,7 +255,7 @@
|
||||
"details_inputs": "Masukan",
|
||||
"details_outputs": "Keluaran",
|
||||
"details_received": "Diterima",
|
||||
"details_show_in_block_explorer": "Lihat di Penjelajah Bongkah.",
|
||||
"details_view_in_browser": "Lihat di Penjelajah Bongkah.",
|
||||
"details_title": "Urus niaga",
|
||||
"details_to": "Keluaran",
|
||||
"enable_offline_signing": "Dompet ini tidak digunakan bersama dengan penandatanganan luar talian. Adakah anda mahu membolehkan ciri ini sekarang?",
|
||||
|
@ -275,7 +275,7 @@
|
||||
"details_inputs": "Inndata",
|
||||
"details_outputs": "Utdata",
|
||||
"details_received": "Mottatt",
|
||||
"details_show_in_block_explorer": "Vis i Block Explorer",
|
||||
"details_view_in_browser": "Vis i Block Explorer",
|
||||
"details_title": "Transaksjon",
|
||||
"details_to": "Utdata",
|
||||
"enable_offline_signing": "Denne lommeboken brukes ikke i forbindelse med en offline-signering. Vil du aktivere det nå?",
|
||||
|
@ -280,7 +280,7 @@
|
||||
"details_inputs": "Inputs",
|
||||
"details_outputs": "Outputs",
|
||||
"details_received": "Ontvangen",
|
||||
"details_show_in_block_explorer": "Weergeven in block explorer",
|
||||
"details_view_in_browser": "Weergeven in block explorer",
|
||||
"details_title": "Transacties",
|
||||
"details_to": "Uitvoer",
|
||||
"enable_offline_signing": "Deze wallet wordt niet gebruikt in combinatie met een hardware wallet. Wilt u het gebruik inschakelen?",
|
||||
|
@ -333,7 +333,7 @@
|
||||
"details_outputs": "Wyjścia",
|
||||
"date": "Data",
|
||||
"details_received": "Otrzymano",
|
||||
"details_show_in_block_explorer": "Zobacz w eksploratorze bloków",
|
||||
"details_view_in_browser": "Zobacz w eksploratorze bloków",
|
||||
"details_title": "Transakcja",
|
||||
"incoming_transaction": "Transakcja przychodząca",
|
||||
"outgoing_transaction": "Transakcja wychodząca",
|
||||
|
@ -333,7 +333,7 @@
|
||||
"details_outputs": "Saídas",
|
||||
"date": "Data",
|
||||
"details_received": "Recebido",
|
||||
"details_show_in_block_explorer": "Ver no Explorador de Blocos",
|
||||
"details_view_in_browser": "Ver no Explorador de Blocos",
|
||||
"details_title": "Transação",
|
||||
"incoming_transaction": "Transação de Entrada",
|
||||
"outgoing_transaction": "Transação de Saída",
|
||||
|
@ -244,7 +244,7 @@
|
||||
"details_outputs": "Outputs",
|
||||
"date": "Data",
|
||||
"details_received": "Recebido",
|
||||
"details_show_in_block_explorer": "Mostrar no block explorer",
|
||||
"details_view_in_browser": "Mostrar no block explorer",
|
||||
"details_title": "detalhes",
|
||||
"details_to": "Para",
|
||||
"enable_offline_signing": "Esta carteira não está a ser utilizada em conjunto com uma assinatura offline. Deseja activá-la agora?",
|
||||
|
@ -262,7 +262,7 @@
|
||||
"details_inputs": "Input-uri",
|
||||
"details_outputs": "Output-uri",
|
||||
"details_received": "Primit",
|
||||
"details_show_in_block_explorer": "Afișează în Block Explorer",
|
||||
"details_view_in_browser": "Afișează în Block Explorer",
|
||||
"details_title": "Tranzacție",
|
||||
"details_to": "Output",
|
||||
"enable_offline_signing": "Acest portofel nu este folosit în legătură cu o semnare offline. Ai vrea să activezi asta acum?",
|
||||
|
@ -344,7 +344,7 @@
|
||||
"details_outputs": "Выходы",
|
||||
"date": "Дата",
|
||||
"details_received": "Получена",
|
||||
"details_show_in_block_explorer": "Показать в блокчейне",
|
||||
"details_view_in_browser": "Показать в блокчейне",
|
||||
"details_title": "Детали транзакции",
|
||||
"incoming_transaction": "Входящая транзакция",
|
||||
"outgoing_transaction": "Исходящая транзакция",
|
||||
|
@ -271,7 +271,7 @@
|
||||
"details_inputs": "යෙදවුම්",
|
||||
"details_outputs": "ප්රතිදාන",
|
||||
"details_received": "ලැබුණි",
|
||||
"details_show_in_block_explorer": "බ්ලොක් එක්ස්ප්ලෝරර් හි බලන්න",
|
||||
"details_view_in_browser": "බ්ලොක් එක්ස්ප්ලෝරර් හි බලන්න",
|
||||
"details_title": "ගනුදෙනුව",
|
||||
"details_to": "ප්රතිදානය",
|
||||
"enable_offline_signing": "මෙම පසුම්බිය නොබැඳි අත්සන් කිරීම සමඟ එක්ව භාවිතා නොකෙරේ. ඔබ දැන් එය සක්රීය කිරීමට කැමතිද?",
|
||||
|
@ -156,7 +156,7 @@
|
||||
"details_inputs": "Vstupy",
|
||||
"details_outputs": "Výstupy",
|
||||
"details_received": "Prijaté",
|
||||
"details_show_in_block_explorer": "Ukázať v block exploreri",
|
||||
"details_view_in_browser": "Ukázať v block exploreri",
|
||||
"details_title": "Transakcia",
|
||||
"details_to": "Výstup",
|
||||
"pending": "Čaká...",
|
||||
|
@ -280,7 +280,7 @@
|
||||
"details_outputs": "Izhodi",
|
||||
"date": "Datum",
|
||||
"details_received": "Prejeto",
|
||||
"details_show_in_block_explorer": "Prikaži v raziskovalcu blokov",
|
||||
"details_view_in_browser": "Prikaži v raziskovalcu blokov",
|
||||
"details_title": "Transakcija",
|
||||
"details_to": "Izhod",
|
||||
"enable_offline_signing": "Ta denarnica se ne uporablja skupaj s podpisovanjem brez povezave (offline). Ali ga želite omogočiti?",
|
||||
|
@ -289,7 +289,7 @@
|
||||
"details_outputs": "Outputs",
|
||||
"date": "Datum",
|
||||
"details_received": "Mottaget",
|
||||
"details_show_in_block_explorer": "Visa i block explorer",
|
||||
"details_view_in_browser": "Visa i block explorer",
|
||||
"details_title": "Transaktion",
|
||||
"details_to": "Output",
|
||||
"enable_offline_signing": "Den här plånboken används inte i samband med en offlinesignering. Skulle du vilja aktivera det nu?",
|
||||
|
@ -201,7 +201,7 @@
|
||||
"details_inputs": "อินพุท",
|
||||
"details_outputs": "เอ้าพุท",
|
||||
"details_received": "ได้รับแล้ว",
|
||||
"details_show_in_block_explorer": "แสดงด้วย block explorer",
|
||||
"details_view_in_browser": "แสดงด้วย block explorer",
|
||||
"details_title": "ธุรกรรม",
|
||||
"details_to": "เอ้าพุท",
|
||||
"pending": "รอดำเนินการ",
|
||||
|
@ -159,7 +159,7 @@
|
||||
"cpfp_create": "Oluştur",
|
||||
"details_copy": "Kopya",
|
||||
"details_from": "Girdi",
|
||||
"details_show_in_block_explorer": "Blok gezgininde göster",
|
||||
"details_view_in_browser": "Blok gezgininde göster",
|
||||
"details_title": "İşlem",
|
||||
"details_to": "Çıktı",
|
||||
"pending": "Beklemede",
|
||||
|
@ -210,7 +210,7 @@
|
||||
"details_copy_amount": "Копіювати Суму",
|
||||
"details_copy_note": "Копіювати Нотатку",
|
||||
"details_from": "Від",
|
||||
"details_show_in_block_explorer": "Show in block explorer",
|
||||
"details_view_in_browser": "Show in block explorer",
|
||||
"details_title": "Деталі транзакції",
|
||||
"details_to": "Кому",
|
||||
"pending": "Очікування",
|
||||
|
@ -283,7 +283,7 @@
|
||||
"details_outputs": "Các đầu ra",
|
||||
"date": "Ngày",
|
||||
"details_received": "Đã nhận",
|
||||
"details_show_in_block_explorer": "Xem trong Block Explorer",
|
||||
"details_view_in_browser": "Xem trong Block Explorer",
|
||||
"details_title": "Giao dịch",
|
||||
"details_to": "Đầu ra",
|
||||
"enable_offline_signing": "Ví này không được sử dụng cùng với một bản ký ngoại tuyến. Bạn có muốn kích hoạt nó ngay bây giờ không?",
|
||||
|
@ -154,7 +154,7 @@
|
||||
"cpfp_create": "Skep",
|
||||
"details_copy": "Kopieer",
|
||||
"details_from": "Inset",
|
||||
"details_show_in_block_explorer": "Wys in blok verkenner",
|
||||
"details_view_in_browser": "Wys in blok verkenner",
|
||||
"details_title": "Transaksie",
|
||||
"details_to": "Resultaat",
|
||||
"list_title": "transaksies",
|
||||
|
@ -93,7 +93,7 @@
|
||||
"cpfp_create": "Yakha",
|
||||
"details_copy": "Ikopi",
|
||||
"details_from": "Negalelo",
|
||||
"details_show_in_block_explorer": "Bonisa ibhloko umhloi",
|
||||
"details_view_in_browser": "Bonisa ibhloko umhloi",
|
||||
"details_title": "Ngeniswa",
|
||||
"details_to": "Mveliso",
|
||||
"list_title": "ngeniswa",
|
||||
|
@ -241,7 +241,7 @@
|
||||
"details_inputs": "输入",
|
||||
"details_outputs": "输出",
|
||||
"details_received": "已收到",
|
||||
"details_show_in_block_explorer": "在区块浏览器查看",
|
||||
"details_view_in_browser": "在区块浏览器查看",
|
||||
"details_title": "转账",
|
||||
"details_to": "输出",
|
||||
"enable_offline_signing": "此钱包未与线下签名结合使用。您想立即启用它吗?",
|
||||
|
@ -237,7 +237,7 @@
|
||||
"details_inputs": "輸入",
|
||||
"details_outputs": "輸出",
|
||||
"details_received": "已收到",
|
||||
"details_show_in_block_explorer": "區塊瀏覽器展示",
|
||||
"details_view_in_browser": "區塊瀏覽器展示",
|
||||
"details_title": "轉賬",
|
||||
"details_to": "輸出",
|
||||
"enable_offline_signing": "此錢包未與線下簽名結合使用。您想立即啟用它嗎?",
|
||||
|
@ -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;
|
||||
|
85
package-lock.json
generated
85
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",
|
||||
@ -86,11 +86,11 @@
|
||||
"react-native-randombytes": "3.6.1",
|
||||
"react-native-rate": "1.2.12",
|
||||
"react-native-reanimated": "3.16.0",
|
||||
"react-native-safe-area-context": "4.11.1",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
|
||||
"react-native-screens": "3.34.0",
|
||||
"react-native-screens": "3.35.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",
|
||||
@ -20869,9 +20876,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-safe-area-context": {
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.11.1.tgz",
|
||||
"integrity": "sha512-urF1m4nFiZFaWjsv2zj8J/hKvo4b2tJW+6CYU1mY4lKv1RwhG2eV8J/EHKuNlLhATZx3+6j7szrpHrQW2ZcAaQ==",
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.12.0.tgz",
|
||||
"integrity": "sha512-ukk5PxcF4p3yu6qMZcmeiZgowhb5AsKRnil54YFUUAXVIS7PJcMHGGC+q44fCiBg44/1AJk5njGMez1m9H0BVQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
@ -20888,9 +20895,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-screens": {
|
||||
"version": "3.34.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.34.0.tgz",
|
||||
"integrity": "sha512-8ri3Pd9QcpfXnVckOe/Lnto+BXmSPHV/Q0RB0XW0gDKsCv5wi5k7ez7g1SzgiYHl29MSdiqgjH30zUyOOowOaw==",
|
||||
"version": "3.35.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.35.0.tgz",
|
||||
"integrity": "sha512-rmkqb/M/SQIrXwygk6pXcOhgHltYAhidf1WceO7ujAxkr6XtwmgFyd1HIztsrJa568GrAuwPdQ11I7TpVk+XsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-freeze": "^1.0.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"
|
||||
|
20
package.json
20
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",
|
||||
@ -150,11 +150,11 @@
|
||||
"react-native-randombytes": "3.6.1",
|
||||
"react-native-rate": "1.2.12",
|
||||
"react-native-reanimated": "3.16.0",
|
||||
"react-native-safe-area-context": "4.11.1",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screen-capture": "github:BlueWallet/react-native-screen-capture#18cb79f",
|
||||
"react-native-screens": "3.34.0",
|
||||
"react-native-screens": "3.35.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;
|
@ -165,7 +165,7 @@ const ReceiveDetails = () => {
|
||||
}, [showConfirmedBalance]);
|
||||
|
||||
const toolTipActions = useMemo(() => {
|
||||
const action = CommonToolTipActions.PaymentCode;
|
||||
const action = CommonToolTipActions.PaymentsCode;
|
||||
action.menuState = wallet?.isBIP47Enabled();
|
||||
return [action];
|
||||
}, [wallet]);
|
||||
|
@ -160,7 +160,7 @@ const Broadcast: React.FC = () => {
|
||||
<BlueSpacing20 />
|
||||
</BlueCard>
|
||||
)}
|
||||
{BROADCAST_RESULT.success === broadcastResult && tx && <SuccessScreen tx={tx} url={`${selectedBlockExplorer}/tx/${tx}`} />}
|
||||
{BROADCAST_RESULT.success === broadcastResult && tx && <SuccessScreen tx={tx} url={`${selectedBlockExplorer.url}/tx/${tx}`} />}
|
||||
</View>
|
||||
</SafeArea>
|
||||
);
|
||||
|
@ -51,13 +51,13 @@ import { SendDetailsStackParamList } from '../../navigation/SendDetailsStackPara
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import { ContactList } from '../../class/contact-list';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { Action } from '../../components/types';
|
||||
import SelectFeeModal from '../../components/SelectFeeModal';
|
||||
import { useKeyboard } from '../../hooks/useKeyboard';
|
||||
import { DismissKeyboardInputAccessory, DismissKeyboardInputAccessoryViewID } from '../../components/DismissKeyboardInputAccessory';
|
||||
import ActionSheet from '../ActionSheet';
|
||||
import HeaderMenuButton from '../../components/HeaderMenuButton';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import { Action } from '../../components/types';
|
||||
|
||||
interface IPaymentDestinations {
|
||||
address: string; // btc address or payment code
|
||||
@ -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),
|
||||
});
|
||||
};
|
||||
|
||||
@ -956,23 +942,23 @@ const SendDetails = () => {
|
||||
handleAddRecipient();
|
||||
} else if (id === CommonToolTipActions.RemoveRecipient.id) {
|
||||
handleRemoveRecipient();
|
||||
} else if (id === SendDetails.actionKeys.SignPSBT) {
|
||||
} else if (id === CommonToolTipActions.SignPSBT.id) {
|
||||
handlePsbtSign();
|
||||
} else if (id === SendDetails.actionKeys.SendMax) {
|
||||
} else if (id === CommonToolTipActions.SendMax.id) {
|
||||
onUseAllPressed();
|
||||
} else if (id === SendDetails.actionKeys.AllowRBF) {
|
||||
} else if (id === CommonToolTipActions.AllowRBF.id) {
|
||||
onReplaceableFeeSwitchValueChanged(!isTransactionReplaceable);
|
||||
} else if (id === SendDetails.actionKeys.ImportTransaction) {
|
||||
} else if (id === CommonToolTipActions.ImportTransaction.id) {
|
||||
importTransaction();
|
||||
} else if (id === SendDetails.actionKeys.ImportTransactionQR) {
|
||||
} else if (id === CommonToolTipActions.ImportTransactionQR.id) {
|
||||
importQrTransaction();
|
||||
} else if (id === SendDetails.actionKeys.ImportTransactionMultsig) {
|
||||
} else if (id === CommonToolTipActions.ImportTransactionMultsig.id) {
|
||||
importTransactionMultisig();
|
||||
} else if (id === SendDetails.actionKeys.CoSignTransaction) {
|
||||
} else if (id === CommonToolTipActions.CoSignTransaction.id) {
|
||||
importTransactionMultisigScanQr();
|
||||
} else if (id === SendDetails.actionKeys.CoinControl) {
|
||||
} else if (id === CommonToolTipActions.CoinControl.id) {
|
||||
handleCoinControl();
|
||||
} else if (id === SendDetails.actionKeys.InsertContact) {
|
||||
} else if (id === CommonToolTipActions.InsertContact.id) {
|
||||
handleInsertContact();
|
||||
} else if (id === CommonToolTipActions.RemoveAllRecipients.id) {
|
||||
handleRemoveAllRecipients();
|
||||
@ -980,66 +966,73 @@ const SendDetails = () => {
|
||||
};
|
||||
|
||||
const headerRightActions = () => {
|
||||
const actions: Action[] & Action[][] = [];
|
||||
if (isEditable) {
|
||||
if (wallet?.allowBIP47() && wallet?.isBIP47Enabled()) {
|
||||
actions.push([
|
||||
{ id: SendDetails.actionKeys.InsertContact, text: loc.send.details_insert_contact, icon: SendDetails.actionIcons.InsertContact },
|
||||
]);
|
||||
}
|
||||
if (!wallet) return [];
|
||||
|
||||
if (Number(wallet?.getBalance()) > 0) {
|
||||
const isSendMaxUsed = addresses.some(element => element.amount === BitcoinUnit.MAX);
|
||||
const walletActions: Action[][] = [];
|
||||
|
||||
actions.push([{ id: SendDetails.actionKeys.SendMax, text: loc.send.details_adv_full, disabled: balance === 0 || isSendMaxUsed }]);
|
||||
}
|
||||
if (wallet?.type === HDSegwitBech32Wallet.type && isTransactionReplaceable !== undefined) {
|
||||
actions.push([{ id: SendDetails.actionKeys.AllowRBF, text: loc.send.details_adv_fee_bump, menuState: !!isTransactionReplaceable }]);
|
||||
}
|
||||
const transactionActions = [];
|
||||
if (wallet?.type === WatchOnlyWallet.type && wallet.isHd()) {
|
||||
transactionActions.push(
|
||||
{
|
||||
id: SendDetails.actionKeys.ImportTransaction,
|
||||
text: loc.send.details_adv_import,
|
||||
icon: SendDetails.actionIcons.ImportTransaction,
|
||||
},
|
||||
{
|
||||
id: SendDetails.actionKeys.ImportTransactionQR,
|
||||
text: loc.send.details_adv_import_qr,
|
||||
icon: SendDetails.actionIcons.ImportTransactionQR,
|
||||
},
|
||||
);
|
||||
}
|
||||
if (wallet?.type === MultisigHDWallet.type) {
|
||||
transactionActions.push({
|
||||
id: SendDetails.actionKeys.ImportTransactionMultsig,
|
||||
text: loc.send.details_adv_import,
|
||||
icon: SendDetails.actionIcons.ImportTransactionMultsig,
|
||||
});
|
||||
}
|
||||
if (wallet?.type === MultisigHDWallet.type && wallet.howManySignaturesCanWeMake() > 0) {
|
||||
transactionActions.push({
|
||||
id: SendDetails.actionKeys.CoSignTransaction,
|
||||
text: loc.multisig.co_sign_transaction,
|
||||
icon: SendDetails.actionIcons.SignPSBT,
|
||||
});
|
||||
}
|
||||
if ((wallet as MultisigHDWallet)?.allowCosignPsbt()) {
|
||||
transactionActions.push({ id: SendDetails.actionKeys.SignPSBT, text: loc.send.psbt_sign, icon: SendDetails.actionIcons.SignPSBT });
|
||||
}
|
||||
actions.push(transactionActions);
|
||||
const recipientActions: Action[] = [
|
||||
CommonToolTipActions.AddRecipient,
|
||||
CommonToolTipActions.RemoveRecipient,
|
||||
{
|
||||
...CommonToolTipActions.RemoveAllRecipients,
|
||||
hidden: !(addresses.length > 1),
|
||||
},
|
||||
];
|
||||
walletActions.push(recipientActions);
|
||||
|
||||
const recipientActions: Action[] = [CommonToolTipActions.AddRecipient, CommonToolTipActions.RemoveRecipient];
|
||||
if (addresses.length > 1) {
|
||||
recipientActions.push(CommonToolTipActions.RemoveAllRecipients);
|
||||
}
|
||||
actions.push(recipientActions);
|
||||
}
|
||||
const isSendMaxUsed = addresses.some(element => element.amount === BitcoinUnit.MAX);
|
||||
const sendMaxAction: Action[] = [
|
||||
{
|
||||
...CommonToolTipActions.SendMax,
|
||||
disabled: wallet.getBalance() === 0 || isSendMaxUsed,
|
||||
hidden: !isEditable || !(Number(wallet.getBalance()) > 0),
|
||||
},
|
||||
];
|
||||
walletActions.push(sendMaxAction);
|
||||
|
||||
actions.push({ id: SendDetails.actionKeys.CoinControl, text: loc.cc.header, icon: SendDetails.actionIcons.CoinControl });
|
||||
const rbfAction: Action[] = [
|
||||
{
|
||||
...CommonToolTipActions.AllowRBF,
|
||||
menuState: isTransactionReplaceable,
|
||||
hidden: !(wallet.type === HDSegwitBech32Wallet.type && isTransactionReplaceable !== undefined),
|
||||
},
|
||||
];
|
||||
walletActions.push(rbfAction);
|
||||
|
||||
return actions;
|
||||
const transactionActions: Action[] = [
|
||||
{
|
||||
...CommonToolTipActions.ImportTransaction,
|
||||
hidden: !(wallet.type === WatchOnlyWallet.type && wallet.isHd()),
|
||||
},
|
||||
{
|
||||
...CommonToolTipActions.ImportTransactionQR,
|
||||
hidden: !(wallet.type === WatchOnlyWallet.type && wallet.isHd()),
|
||||
},
|
||||
{
|
||||
...CommonToolTipActions.ImportTransactionMultsig,
|
||||
hidden: !(wallet.type === MultisigHDWallet.type),
|
||||
},
|
||||
{
|
||||
...CommonToolTipActions.CoSignTransaction,
|
||||
hidden: !(wallet.type === MultisigHDWallet.type && wallet.howManySignaturesCanWeMake() > 0),
|
||||
},
|
||||
{
|
||||
...CommonToolTipActions.SignPSBT,
|
||||
hidden: !(wallet as MultisigHDWallet)?.allowCosignPsbt(),
|
||||
},
|
||||
];
|
||||
walletActions.push(transactionActions);
|
||||
|
||||
const specificWalletActions: Action[] = [
|
||||
{
|
||||
...CommonToolTipActions.InsertContact,
|
||||
hidden: !(isEditable && wallet.allowBIP47() && wallet.isBIP47Enabled()),
|
||||
},
|
||||
CommonToolTipActions.CoinControl,
|
||||
];
|
||||
walletActions.push(specificWalletActions);
|
||||
|
||||
return walletActions;
|
||||
};
|
||||
|
||||
const setHeaderRightOptions = () => {
|
||||
@ -1050,7 +1043,7 @@ const SendDetails = () => {
|
||||
};
|
||||
|
||||
const onReplaceableFeeSwitchValueChanged = (value: boolean) => {
|
||||
setIsTransactionReplaceable(value);
|
||||
setParams({ isTransactionReplaceable: value });
|
||||
};
|
||||
|
||||
const handleRecipientsScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
@ -1141,16 +1134,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 +1255,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 +1349,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)} />
|
||||
),
|
||||
})}
|
||||
@ -1366,29 +1361,6 @@ const SendDetails = () => {
|
||||
|
||||
export default SendDetails;
|
||||
|
||||
SendDetails.actionKeys = {
|
||||
InsertContact: 'InsertContact',
|
||||
SignPSBT: 'SignPSBT',
|
||||
SendMax: 'SendMax',
|
||||
AllowRBF: 'AllowRBF',
|
||||
ImportTransaction: 'ImportTransaction',
|
||||
ImportTransactionMultsig: 'ImportTransactionMultisig',
|
||||
ImportTransactionQR: 'ImportTransactionQR',
|
||||
CoinControl: 'CoinControl',
|
||||
CoSignTransaction: 'CoSignTransaction',
|
||||
};
|
||||
|
||||
SendDetails.actionIcons = {
|
||||
InsertContact: { iconValue: 'at.badge.plus' },
|
||||
SignPSBT: { iconValue: 'signature' },
|
||||
SendMax: 'SendMax',
|
||||
AllowRBF: 'AllowRBF',
|
||||
ImportTransaction: { iconValue: 'square.and.arrow.down' },
|
||||
ImportTransactionMultsig: { iconValue: 'square.and.arrow.down.on.square' },
|
||||
ImportTransactionQR: { iconValue: 'qrcode.viewfinder' },
|
||||
CoinControl: { iconValue: 'switch.2' },
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
flex: 1,
|
||||
|
@ -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,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Platform, Pressable, ScrollView, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { Platform, ScrollView, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { openSettings } from 'react-native-permissions';
|
||||
import A from '../../blue_modules/analytics';
|
||||
import { Header } from '../../components/Header';
|
||||
@ -127,11 +127,7 @@ const SettingsPrivacy: React.FC = () => {
|
||||
disabled: isLoading === SettingsPrivacySection.All,
|
||||
testID: 'ClipboardSwitch',
|
||||
}}
|
||||
subtitle={
|
||||
<Pressable accessibilityRole="button">
|
||||
<Text style={styles.subtitleText}>{loc.settings.privacy_clipboard_explanation}</Text>
|
||||
</Pressable>
|
||||
}
|
||||
subtitle={loc.settings.privacy_clipboard_explanation}
|
||||
/>
|
||||
|
||||
<ListItem
|
||||
|
@ -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',
|
||||
},
|
||||
});
|
@ -347,7 +347,7 @@ const TransactionDetails = () => {
|
||||
onPress={handleOnOpenTransactionOnBlockExplorerTapped}
|
||||
buttonStyle={StyleSheet.flatten([styles.greyButton, stylesHooks.greyButton])}
|
||||
>
|
||||
<Text style={[styles.Link, stylesHooks.Link]}>{loc.transactions.details_show_in_block_explorer}</Text>
|
||||
<Text style={[styles.Link, stylesHooks.Link]}>{loc.transactions.details_view_in_browser}</Text>
|
||||
</ToolTipMenu>
|
||||
</BlueCard>
|
||||
</ScrollView>
|
||||
|
@ -61,7 +61,7 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
// change currency to ARS ($) and switch it back to USD ($)
|
||||
await element(by.id('Currency')).tap();
|
||||
await element(by.text('ARS ($)')).tap();
|
||||
await expect(element(by.text('Price is obtained from Yadio'))).toBeVisible();
|
||||
await expect(element(by.text('Rate is obtained from Yadio'))).toBeVisible();
|
||||
await element(by.text('USD ($)')).tap();
|
||||
await device.pressBack();
|
||||
|
||||
@ -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'));
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Platform } from 'react-native';
|
||||
import loc from '../loc';
|
||||
import { Action } from '../components/types';
|
||||
|
||||
const keys = {
|
||||
CopyTXID: 'copyTX_ID',
|
||||
@ -20,68 +22,71 @@ const keys = {
|
||||
SaveChanges: 'saveChanges',
|
||||
ClearClipboard: 'clearClipboard',
|
||||
PaymentsCode: 'paymentsCode',
|
||||
ResetToDefault: 'resetToDefault',
|
||||
ClearHistory: 'clearHistory',
|
||||
ScanQR: 'scan_qr',
|
||||
RemoveAllRecipients: 'RemoveAllRecipients',
|
||||
AddRecipient: 'AddRecipient',
|
||||
RemoveRecipient: 'RemoveRecipient',
|
||||
ChoosePhoto: 'choose_photo',
|
||||
ImportFile: 'import_file',
|
||||
InsertContact: 'insert_contact',
|
||||
SignPSBT: 'sign_psbt',
|
||||
SendMax: 'send_max',
|
||||
AllowRBF: 'allow_rbf',
|
||||
ImportTransaction: 'import_transaction',
|
||||
ImportTransactionMultsig: 'import_transaction_multisig',
|
||||
ImportTransactionQR: 'import_transaction_qr',
|
||||
CoinControl: 'coin_control',
|
||||
CoSignTransaction: 'co_sign_transaction',
|
||||
CopyToClipboard: 'copyToClipboard',
|
||||
Share: 'share',
|
||||
SignVerify: 'signVerify',
|
||||
ExportPrivateKey: 'exportPrivateKey',
|
||||
PasteFromClipboard: 'pasteFromClipboard',
|
||||
};
|
||||
|
||||
const icons = {
|
||||
Eye: {
|
||||
iconValue: 'eye',
|
||||
},
|
||||
EyeSlash: {
|
||||
iconValue: 'eye.slash',
|
||||
},
|
||||
Clipboard: {
|
||||
iconValue: 'doc.on.doc',
|
||||
},
|
||||
ClearClipboard: {
|
||||
iconValue: 'clipboard',
|
||||
},
|
||||
Link: {
|
||||
iconValue: 'link',
|
||||
},
|
||||
Note: {
|
||||
iconValue: 'note.text',
|
||||
},
|
||||
ManageWallets: {
|
||||
iconValue: 'slider.horizontal.3',
|
||||
},
|
||||
ImportWallet: {
|
||||
iconValue: 'square.and.arrow.down.on.square',
|
||||
},
|
||||
ViewInBitcoin: {
|
||||
iconValue: 'bitcoinsign.circle',
|
||||
},
|
||||
ViewInFiat: {
|
||||
iconValue: 'coloncurrencysign.circle',
|
||||
},
|
||||
Entropy: {
|
||||
iconValue: 'dice',
|
||||
},
|
||||
SearchAccount: {
|
||||
iconValue: 'magnifyingglass',
|
||||
},
|
||||
Passphrase: {
|
||||
iconValue: 'rectangle.and.pencil.and.ellipsis',
|
||||
},
|
||||
MoreInfo: {
|
||||
iconValue: 'info.circle',
|
||||
},
|
||||
SaveChanges: {
|
||||
iconValue: 'checkmark',
|
||||
},
|
||||
PaymentsCode: {
|
||||
iconValue: 'qrcode',
|
||||
const icons: { [key: string]: { iconValue: string } } = {
|
||||
Eye: { iconValue: 'eye' },
|
||||
EyeSlash: { iconValue: 'eye.slash' },
|
||||
Link: { iconValue: 'link' },
|
||||
Note: { iconValue: 'note.text' },
|
||||
ManageWallets: { iconValue: 'slider.horizontal.3' },
|
||||
ImportWallet: { iconValue: 'square.and.arrow.down.on.square' },
|
||||
ViewInBitcoin: { iconValue: 'bitcoinsign.circle' },
|
||||
ViewInFiat: { iconValue: 'coloncurrencysign.circle' },
|
||||
Entropy: { iconValue: 'dice' },
|
||||
SearchAccount: { iconValue: 'magnifyingglass' },
|
||||
Passphrase: { iconValue: 'rectangle.and.pencil.and.ellipsis' },
|
||||
MoreInfo: { iconValue: 'info.circle' },
|
||||
SaveChanges: { iconValue: 'checkmark' },
|
||||
InsertContact: { iconValue: 'at.badge.plus' },
|
||||
SignPSBT: { iconValue: 'signature' },
|
||||
SendMax: { iconValue: 'dial.high' },
|
||||
AllowRBF: { iconValue: 'arrowshape.up.circle' },
|
||||
ImportTransaction: { iconValue: 'square.and.arrow.down' },
|
||||
ImportTransactionMultsig: { iconValue: 'square.and.arrow.down.on.square' },
|
||||
ImportTransactionQR: { iconValue: 'qrcode.viewfinder' },
|
||||
CoinControl: { iconValue: 'switch.2' },
|
||||
CoSignTransaction: { iconValue: 'signature' },
|
||||
PaymentsCode: { iconValue: 'qrcode.viewfinder' },
|
||||
ClearHistory: {
|
||||
iconValue: 'trash',
|
||||
},
|
||||
RemoveAllRecipients: { iconValue: 'person.2.slash' },
|
||||
AddRecipient: { iconValue: 'person.badge.plus' },
|
||||
RemoveRecipient: { iconValue: 'person.badge.minus' },
|
||||
ScanQR: { iconValue: Platform.OS === 'ios' ? 'qrcode.viewfinder' : 'ic_menu_camera' },
|
||||
ChoosePhoto: { iconValue: Platform.OS === 'ios' ? 'photo.on.rectangle' : 'ic_menu_gallery' },
|
||||
Clipboard: { iconValue: Platform.OS === 'ios' ? 'document.on.clipboard' : 'ic_menu_file' },
|
||||
ExportPrivateKey: { iconValue: 'key' },
|
||||
Share: { iconValue: 'square.and.arrow.up' },
|
||||
Signature: { iconValue: 'signature' },
|
||||
PasteFromClipboard: { iconValue: 'document.on.clipboard' },
|
||||
ImportFile: { iconValue: 'document.viewfinder' },
|
||||
};
|
||||
|
||||
export const CommonToolTipActions = {
|
||||
export const CommonToolTipActions: { [key: string]: Action } = {
|
||||
CopyTXID: {
|
||||
id: keys.CopyTXID,
|
||||
text: loc.transactions.details_copy_txid,
|
||||
@ -94,7 +99,7 @@ export const CommonToolTipActions = {
|
||||
},
|
||||
OpenInBlockExplorer: {
|
||||
id: keys.OpenInBlockExplorer,
|
||||
text: loc.transactions.details_show_in_block_explorer,
|
||||
text: loc.transactions.details_view_in_browser,
|
||||
icon: icons.Link,
|
||||
},
|
||||
ExpandNote: {
|
||||
@ -141,16 +146,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,
|
||||
@ -185,15 +193,114 @@ export const CommonToolTipActions = {
|
||||
text: loc._.save,
|
||||
icon: icons.SaveChanges,
|
||||
},
|
||||
PaymentCode: {
|
||||
PaymentsCode: {
|
||||
id: keys.PaymentsCode,
|
||||
text: loc.bip47.purpose,
|
||||
icon: icons.PaymentsCode,
|
||||
menuState: false,
|
||||
},
|
||||
ScanQR: {
|
||||
id: keys.ScanQR,
|
||||
text: loc.wallets.list_long_scan,
|
||||
icon: icons.ScanQR,
|
||||
},
|
||||
ChoosePhoto: {
|
||||
id: keys.ChoosePhoto,
|
||||
text: loc.wallets.list_long_choose,
|
||||
icon: icons.ChoosePhoto,
|
||||
},
|
||||
ImportFile: {
|
||||
id: keys.ImportFile,
|
||||
text: loc.wallets.import_file,
|
||||
icon: icons.ImportFile,
|
||||
},
|
||||
InsertContact: {
|
||||
id: keys.InsertContact,
|
||||
text: loc.send.details_insert_contact,
|
||||
icon: icons.InsertContact,
|
||||
hidden: true,
|
||||
},
|
||||
SignPSBT: {
|
||||
id: keys.SignPSBT,
|
||||
text: loc.send.psbt_sign,
|
||||
icon: icons.SignPSBT,
|
||||
hidden: true,
|
||||
},
|
||||
SendMax: {
|
||||
id: keys.SendMax,
|
||||
text: loc.send.details_adv_full,
|
||||
icon: icons.SendMax,
|
||||
hidden: true,
|
||||
},
|
||||
AllowRBF: {
|
||||
id: keys.AllowRBF,
|
||||
text: loc.send.details_adv_fee_bump,
|
||||
icon: icons.AllowRBF,
|
||||
hidden: true,
|
||||
menuState: false,
|
||||
},
|
||||
ImportTransaction: {
|
||||
id: keys.ImportTransaction,
|
||||
text: loc.send.details_adv_import,
|
||||
icon: icons.ImportTransaction,
|
||||
hidden: true,
|
||||
},
|
||||
ImportTransactionQR: {
|
||||
id: keys.ImportTransactionQR,
|
||||
text: loc.send.details_adv_import_qr,
|
||||
icon: icons.ImportTransactionQR,
|
||||
hidden: true,
|
||||
},
|
||||
ImportTransactionMultsig: {
|
||||
id: keys.ImportTransactionMultsig,
|
||||
text: loc.send.details_adv_import,
|
||||
icon: icons.ImportTransactionMultsig,
|
||||
hidden: true,
|
||||
},
|
||||
CoSignTransaction: {
|
||||
id: keys.CoSignTransaction,
|
||||
text: loc.multisig.co_sign_transaction,
|
||||
icon: icons.CoSignTransaction,
|
||||
hidden: true,
|
||||
},
|
||||
CoinControl: {
|
||||
id: keys.CoinControl,
|
||||
text: loc.cc.header,
|
||||
icon: icons.CoinControl,
|
||||
hidden: false,
|
||||
},
|
||||
CopyToClipboard: {
|
||||
id: keys.CopyToClipboard,
|
||||
text: loc.transactions.details_copy,
|
||||
icon: icons.Clipboard,
|
||||
},
|
||||
Share: {
|
||||
id: keys.Share,
|
||||
text: loc.receive.details_share,
|
||||
icon: icons.Share,
|
||||
},
|
||||
SignVerify: {
|
||||
id: keys.SignVerify,
|
||||
text: loc.addresses.sign_title,
|
||||
icon: icons.Signature,
|
||||
},
|
||||
ExportPrivateKey: {
|
||||
id: keys.ExportPrivateKey,
|
||||
text: loc.addresses.copy_private_key,
|
||||
icon: icons.ExportPrivateKey,
|
||||
},
|
||||
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,
|
||||
text: loc.wallets.paste_from_clipboard,
|
||||
icon: icons.PasteFromClipboard,
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user