Merge branch 'master' into rn738

This commit is contained in:
Marcos Rodriguez Velez 2024-05-28 19:47:37 -04:00
commit 14c80f5cf6
No known key found for this signature in database
GPG key ID: 6030B2F48CCE86D7
31 changed files with 739 additions and 566 deletions

View file

@ -93,6 +93,8 @@ export const BlueStorageProvider = ({ children }: { children: React.ReactNode })
useEffect(() => {
BlueElectrum.isDisabled().then(setIsElectrumDisabled);
if (walletsInitialized) {
txMetadata.current = BlueApp.tx_metadata;
counterpartyMetadata.current = BlueApp.counterparty_metadata;
setWallets(BlueApp.getWallets());
BlueElectrum.connectMain();
}

View file

@ -1,6 +1,5 @@
import { useNavigation } from '@react-navigation/native';
import React from 'react';
import { Image, Keyboard, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { Image, Keyboard, StyleSheet, Text, TextInput, View } from 'react-native';
import { scanQrHelper } from '../helpers/scan-qr';
import loc from '../loc';
@ -51,7 +50,6 @@ const AddressInput = ({
keyboardType = 'default',
}: AddressInputProps) => {
const { colors } = useTheme();
const { navigate } = useNavigation();
const stylesHook = StyleSheet.create({
root: {
borderColor: colors.formBorder,
@ -77,7 +75,7 @@ const AddressInput = ({
case actionKeys.ScanQR:
scanButtonTapped();
if (launchedBy) {
scanQrHelper(navigate, launchedBy)
scanQrHelper(launchedBy)
.then(value => onBarScanned({ data: value }))
.catch(error => {
presentAlert({ message: error.message });
@ -137,26 +135,25 @@ const AddressInput = ({
keyboardType={keyboardType}
/>
{editable ? (
<ToolTipMenu actions={actions} isButton onPressMenuItem={onMenuItemPressed}>
<TouchableOpacity
testID="BlueAddressInputScanQrButton"
disabled={isLoading}
onPress={async () => {
await scanButtonTapped();
Keyboard.dismiss();
// @ts-ignore: Fix later
scanQrHelper(navigate, launchedBy).then(onBarScanned);
}}
accessibilityRole="button"
style={[styles.scan, stylesHook.scan]}
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>
</TouchableOpacity>
<ToolTipMenu
actions={actions}
isButton
onPressMenuItem={onMenuItemPressed}
testID="BlueAddressInputScanQrButton"
disabled={isLoading}
onPress={async () => {
await scanButtonTapped();
Keyboard.dismiss();
if (launchedBy) scanQrHelper(launchedBy).then(value => onBarScanned({ data: value }));
}}
style={[styles.scan, stylesHook.scan]}
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>
) : null}
</View>

View file

@ -12,11 +12,12 @@ interface HeaderRightButtonProps {
const HeaderRightButton: React.FC<HeaderRightButtonProps> = ({ disabled = true, onPress, title, testID }) => {
const { colors } = useTheme();
const opacity = disabled ? 0.5 : 1;
return (
<TouchableOpacity
accessibilityRole="button"
disabled={disabled}
style={[styles.save, { backgroundColor: colors.lightButton }]}
style={[styles.save, { backgroundColor: colors.lightButton }, { opacity }]}
onPress={onPress}
testID={testID}
>

View file

@ -7,7 +7,7 @@ import { useTheme } from './themes';
// Update the type for the props
interface ListItemProps {
rightIcon?: any;
leftAvatar?: React.Component;
leftAvatar?: React.JSX.Element;
containerStyle?: object;
Component?: typeof React.Component | typeof PressableWrapper;
bottomDivider?: boolean;
@ -27,6 +27,7 @@ interface ListItemProps {
isLoading?: boolean;
chevron?: boolean;
checkmark?: boolean;
subtitleProps?: object;
}
export class PressableWrapper extends React.Component<PressableProps> {

View file

@ -44,8 +44,15 @@ const menuActions: Action[] =
text: loc.transactions.details_copy,
icon: actionIcons.Copy,
},
{ id: actionKeys.Share, text: loc.receive.details_share, icon: actionIcons.Share },
]
: [{ id: actionKeys.Share, text: loc.receive.details_share, icon: actionIcons.Share }];
: [
{
id: actionKeys.Copy,
text: loc.transactions.details_copy,
icon: actionIcons.Copy,
},
];
const QRCodeComponent: React.FC<QRCodeComponentProps> = ({
value = '',

View file

@ -16,6 +16,7 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<any>) => {
onMenuWillHide,
buttonStyle,
onPressMenuItem,
...restProps
} = props;
const menuItemMapped = useCallback(({ action, menuOptions }: { action: Action; menuOptions?: string[] }) => {
@ -70,8 +71,12 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<any>) => {
menuTitle: title,
menuItems,
}}
{...restProps}
style={buttonStyle}
>
{props.children}
<TouchableOpacity onPress={onPress} disabled={disabled} accessibilityRole="button" {...restProps}>
{props.children}
</TouchableOpacity>
</ContextMenuButton>
);
@ -98,7 +103,7 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<any>) => {
: {})}
>
{onPress ? (
<TouchableOpacity accessibilityRole="button" onPress={onPress}>
<TouchableOpacity accessibilityRole="button" onPress={onPress} {...restProps}>
{props.children}
</TouchableOpacity>
) : (
@ -108,7 +113,7 @@ const BaseToolTipMenu = (props: ToolTipMenuProps, ref: Ref<any>) => {
);
return isMenuPrimaryAction && onPress ? (
<TouchableOpacity onPress={onPress} disabled={disabled} accessibilityRole="button" style={buttonStyle}>
<TouchableOpacity onPress={onPress} disabled={disabled} accessibilityRole="button" {...restProps}>
{renderContextMenuButton()}
</TouchableOpacity>
) : isButton ? (

View file

@ -1,9 +1,7 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import Clipboard from '@react-native-clipboard/clipboard';
import { useNavigation } from '@react-navigation/native';
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Linking, StyleSheet, View } from 'react-native';
import { BlueStorageContext } from '../blue_modules/storage-context';
import Lnurl from '../class/lnurl';
import { LightningTransaction, Transaction } from '../class/wallets/types';
@ -16,12 +14,14 @@ import TransactionOutgoingIcon from '../components/icons/TransactionOutgoingIcon
import TransactionPendingIcon from '../components/icons/TransactionPendingIcon';
import loc, { formatBalanceWithoutSuffix, transactionTimeToReadable } from '../loc';
import { BitcoinUnit } from '../models/bitcoinUnits';
import * as NavigationService from '../NavigationService';
import { useSettings } from './Context/SettingsContext';
import ListItem from './ListItem';
import { useTheme } from './themes';
import ToolTipMenu from './TooltipMenu';
import { Action } from './types';
import { Action, ToolTipMenuProps } from './types';
import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { DetailViewStackParamList } from '../navigation/DetailViewStackParamList';
interface TransactionListItemProps {
itemPriceUnit: BitcoinUnit;
@ -29,11 +29,13 @@ interface TransactionListItemProps {
item: Transaction & LightningTransaction; // using type intersection to have less issues with ts
}
type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList>;
export const TransactionListItem: React.FC<TransactionListItemProps> = React.memo(({ item, itemPriceUnit = BitcoinUnit.BTC, walletID }) => {
const [subtitleNumberOfLines, setSubtitleNumberOfLines] = useState(1);
const { colors } = useTheme();
const { navigate } = useNavigation();
const menuRef = useRef();
const { navigate } = useExtendedNavigation<NavigationProps>();
const menuRef = useRef<ToolTipMenuProps>();
const { txMetadata, counterpartyMetadata, wallets } = useContext(BlueStorageContext);
const { preferredFiatCurrency, language } = useSettings();
const containerStyle = useMemo(
@ -200,10 +202,8 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
}, [subtitle]);
const onPress = useCallback(async () => {
// @ts-ignore: idk how to fix
menuRef?.current?.dismissMenu?.();
if (item.hash) {
// @ts-ignore: idk how to fix
navigate('TransactionStatus', { hash: item.hash, walletID });
} else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') {
const lightningWallet = wallets.filter(wallet => wallet?.getID() === item.walletID);
@ -217,8 +217,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
}
const loaded = await LN.loadSuccessfulPayment(paymentHash);
if (loaded) {
NavigationService.navigate('ScanLndInvoiceRoot', {
// @ts-ignore: idk how to fix
navigate('ScanLndInvoiceRoot', {
screen: 'LnurlPaySuccess',
params: {
paymentHash,
@ -232,7 +231,6 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
console.log(e);
}
// @ts-ignore: idk how to fix
navigate('LNDViewInvoice', {
invoice: item,
walletID: lightningWallet[0].getID(),
@ -347,7 +345,6 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
<View style={styles.container}>
<ToolTipMenu ref={menuRef} actions={toolTipActions} onPressMenuItem={onToolTipPress} onPress={onPress}>
<ListItem
// @ts-ignore wtf
leftAvatar={avatar}
title={title}
subtitleNumberOfLines={subtitleNumberOfLines}

View file

@ -12,6 +12,7 @@ import { FiatUnit } from '../models/fiatUnit';
import { BlurredBalanceView } from './BlurredBalanceView';
import { useSettings } from './Context/SettingsContext';
import ToolTipMenu from './TooltipMenu';
import { ToolTipMenuProps } from './types';
interface TransactionsNavigationHeaderProps {
wallet: TWallet;
@ -31,22 +32,17 @@ interface TransactionsNavigationHeaderProps {
}
const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps> = ({
// @ts-ignore: Ugh
wallet: initialWallet,
// @ts-ignore: Ugh
onWalletUnitChange,
// @ts-ignore: Ugh
navigation,
// @ts-ignore: Ugh
onManageFundsPressed,
// @ts-ignore: Ugh
onWalletBalanceVisibilityChange,
}) => {
const [wallet, setWallet] = useState(initialWallet);
const [allowOnchainAddress, setAllowOnchainAddress] = useState(false);
const { preferredFiatCurrency } = useSettings();
const menuRef = useRef(null);
const menuRef = useRef<ToolTipMenuProps>(null);
const verifyIfWalletAllowsOnchainAddress = useCallback(() => {
if (wallet.type === LightningCustodianWallet.type) {
@ -85,8 +81,9 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
};
const changeWalletBalanceUnit = () => {
// @ts-ignore: Ugh
menuRef.current?.dismissMenu();
if (menuRef.current?.dismissMenu) {
menuRef.current.dismissMenu();
}
let newWalletPreferredUnit = wallet.getPreferredBalanceUnit();
if (newWalletPreferredUnit === BitcoinUnit.BTC) {
@ -140,7 +137,6 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
<LinearGradient
colors={WalletGradient.gradientsFor(wallet.type)}
style={styles.lineaderGradient}
// @ts-ignore: Ugh
{...WalletGradient.linearGradientProps(wallet.type)}
>
<Image

View file

@ -1,10 +1,9 @@
import React, { useMemo, useRef } from 'react';
import Clipboard from '@react-native-clipboard/clipboard';
import { useNavigation } from '@react-navigation/native';
import React, { useMemo, useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { ListItem } from 'react-native-elements';
import Share from 'react-native-share';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { useStorage } from '../../blue_modules/storage-context';
import confirm from '../../helpers/confirm';
@ -15,8 +14,10 @@ import presentAlert from '../Alert';
import QRCodeComponent from '../QRCodeComponent';
import { useTheme } from '../themes';
import TooltipMenu from '../TooltipMenu';
import { Action } from '../types';
import { Action, ToolTipMenuProps } from '../types';
import { AddressTypeBadge } from './AddressTypeBadge';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
interface AddressItemProps {
// todo: fix `any` after addresses.js is converted to the church of holy typescript
@ -26,6 +27,8 @@ interface AddressItemProps {
allowSignVerifyMessage: boolean;
}
type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList>;
const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: AddressItemProps) => {
const { wallets } = useStorage();
const { colors } = useTheme();
@ -52,13 +55,18 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
},
});
const { navigate } = useNavigation();
const { navigate } = useNavigation<NavigationProps>();
const menuRef = useRef<ToolTipMenuProps>();
const dismissMenu = () => {
if (menuRef.current?.dismissMenu) {
menuRef.current.dismissMenu();
}
};
const menuRef = useRef();
const navigateToReceive = () => {
// @ts-ignore wtf
menuRef.current?.dismissMenu();
// @ts-ignore wtf
dismissMenu();
navigate('ReceiveDetailsRoot', {
screen: 'ReceiveDetails',
params: {
@ -69,9 +77,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
};
const navigateToSignVerify = () => {
// @ts-ignore wtf
menuRef.current?.dismissMenu();
// @ts-ignore wtf
dismissMenu();
navigate('SignVerifyRoot', {
screen: 'SignVerify',
params: {
@ -114,13 +120,13 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
};
const onToolTipPress = async (id: string) => {
if (id === AddressItem.actionKeys.CopyToClipboard) {
if (id === actionKeys.CopyToClipboard) {
handleCopyPress();
} else if (id === AddressItem.actionKeys.Share) {
} else if (id === actionKeys.Share) {
handleSharePress();
} else if (id === AddressItem.actionKeys.SignVerify) {
} else if (id === actionKeys.SignVerify) {
navigateToSignVerify();
} else if (id === AddressItem.actionKeys.ExportPrivateKey) {
} else if (id === actionKeys.ExportPrivateKey) {
if (await confirm(loc.addresses.sensitive_private_key)) {
if (await isBiometricUseCapableAndEnabled()) {
if (!(await unlockWithBiometrics())) {
@ -171,14 +177,14 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
return render();
};
AddressItem.actionKeys = {
const actionKeys = {
Share: 'share',
CopyToClipboard: 'copyToClipboard',
SignVerify: 'signVerify',
ExportPrivateKey: 'exportPrivateKey',
};
AddressItem.actionIcons = {
const actionIcons = {
Signature: {
iconType: 'SYSTEM',
iconValue: 'signature',
@ -220,30 +226,30 @@ const styles = StyleSheet.create({
const getAvailableActions = ({ allowSignVerifyMessage }: { allowSignVerifyMessage: boolean }): Action[] | Action[][] => {
const actions = [
{
id: AddressItem.actionKeys.CopyToClipboard,
id: actionKeys.CopyToClipboard,
text: loc.transactions.details_copy,
icon: AddressItem.actionIcons.Clipboard,
icon: actionIcons.Clipboard,
},
{
id: AddressItem.actionKeys.Share,
id: actionKeys.Share,
text: loc.receive.details_share,
icon: AddressItem.actionIcons.Share,
icon: actionIcons.Share,
},
];
if (allowSignVerifyMessage) {
actions.push({
id: AddressItem.actionKeys.SignVerify,
id: actionKeys.SignVerify,
text: loc.addresses.sign_title,
icon: AddressItem.actionIcons.Signature,
icon: actionIcons.Signature,
});
}
if (allowSignVerifyMessage) {
actions.push({
id: AddressItem.actionKeys.ExportPrivateKey,
id: actionKeys.ExportPrivateKey,
text: loc.addresses.copy_private_key,
icon: AddressItem.actionIcons.ExportPrivateKey,
icon: actionIcons.ExportPrivateKey,
});
}

View file

@ -1,4 +1,4 @@
import { ViewStyle } from 'react-native';
import { AccessibilityRole, ViewStyle } from 'react-native';
export interface Action {
id: string | number;
@ -16,6 +16,7 @@ export interface ToolTipMenuProps {
actions: Action[] | Action[][];
children: React.ReactNode;
enableAndroidRipple?: boolean;
dismissMenu?: () => void;
onPressMenuItem: (id: string) => void;
title?: string;
isMenuPrimaryAction?: boolean;
@ -23,7 +24,12 @@ export interface ToolTipMenuProps {
renderPreview?: () => React.ReactNode;
onPress?: () => void;
previewValue?: string;
accessibilityRole?: AccessibilityRole;
disabled?: boolean;
testID?: string;
style?: ViewStyle | ViewStyle[];
accessibilityLabel?: string;
accessibilityHint?: string;
buttonStyle?: ViewStyle;
onMenuWillShow?: () => void;
onMenuWillHide?: () => void;

View file

@ -1,23 +1,17 @@
import { Platform } from 'react-native';
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import { navigationRef } from '../NavigationService';
/**
* Helper function that navigates to ScanQR screen, and returns promise that will resolve with the result of a scan,
* and then navigates back. If QRCode scan was closed, promise resolves to null.
*
* @param navigateFunc {function}
* @param currentScreenName {string}
* @param showFileImportButton {boolean}
*
* @param onDismiss {function} - if camera is closed via X button it gets triggered
* @return {Promise<string>}
*/
function scanQrHelper(
navigateFunc: (scr: string | any, params?: any) => void,
currentScreenName: string,
showFileImportButton = true,
onDismiss?: () => void,
): Promise<string | null> {
function scanQrHelper(currentScreenName: string, showFileImportButton = true, onDismiss?: () => void): Promise<string | null> {
return requestCameraAuthorization().then(() => {
return new Promise(resolve => {
const params = {
@ -28,10 +22,10 @@ function scanQrHelper(
params.onBarScanned = function (data: any) {
setTimeout(() => resolve(data.data || data), 1);
navigateFunc({ name: currentScreenName, params: {}, merge: true });
navigationRef.navigate({ name: currentScreenName, params: {}, merge: true });
};
navigateFunc('ScanQRCodeRoot', {
navigationRef.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params,
});

View file

@ -1 +1 @@
BlueWallet - Bitcoin Wallet
BlueWallet - Bitcoin Wallet

View file

@ -120,11 +120,13 @@
"details_create": "Erstelle",
"details_label": "Beschreibung",
"details_setAmount": "Zu erhaltender Betrag",
"details_share": "Teilen",
"header": "Erhalten",
"maxSats": "Der größtmögliche Betrag ist {max} sats",
"maxSatsFull": "Der größtmögliche Betrag ist {max} sats oder {currency}",
"minSats": "Der kleinstmögliche Betrag ist {min} sats",
"minSatsFull": "Der kleinstmögliche Betrag ist {min} sats oder {currency}"
"minSatsFull": "Der kleinstmögliche Betrag ist {min} sats oder {currency}",
"qrcode_for_the_address": "QR-Code für die Adresse"
},
"send": {
"provided_address_is_invoice": "Diese Adresse ist für eine Lightning-Rechnung. Um diese Rechnung zu zahlen ist ein Lightning Wallet zu verwenden.",
@ -165,6 +167,7 @@
"details_next": "Weiter",
"details_no_signed_tx": "Die ausgewählte Datei enthält keine importierbare signierte Transaktion.",
"details_note_placeholder": "Eigene Bezeichnung",
"counterparty_label_placeholder": "Kontaktnamen bearbeiten",
"details_scan": "Scannen",
"details_scan_hint": "Zum Importieren / Scannen zweimal tippen",
"details_total_exceeds_balance": "Der zu sendende Betrag ist größer als der verfügbare Betrag.",
@ -209,6 +212,7 @@
"reset_amount_confirm": "Möchten Du den Betrag zurücksetzen?",
"success_done": "Fertig",
"txSaved": "Die Transaktionsdatei ({filePath}) wurde im Download-Ordner gespeichert.",
"file_saved_at_path": "Die Datei ({fileName}) wurde im Download-Ordner gespeichert.",
"problem_with_psbt": "PSBT-Problem"
},
"settings": {
@ -273,7 +277,9 @@
"encrypt_title": "Sicherheit",
"encrypt_tstorage": "Speicher",
"encrypt_use": "Benutze {type}",
"encrypted_feature_disabled": "Diese Funktion kann bei verschlüsseltem Speicher nicht genutzt werden.",
"encrypt_use_expl": "{type} wird zur Transaktionsdurchführung, zum Entsperren, dem Export oder der Löschung einer Wallet benötigt. {type} ersetzt nicht die Passworteingabe bei verschlüsseltem Speicher.",
"biometrics_fail": "Wenn {type} nicht aktiviert ist oder entsperrt werden kann, alternativ Ihren Gerätepasscode verwenden.",
"general": "Allgemein",
"general_adv_mode": "Erweiterter Modus",
"general_adv_mode_e": "Erlaubt, wenn aktiviert, verschiedene Wallet-Typen anzulegen, dabei eine benutzerdefinierte Entropie zu verwenden und die LNDHub-Instanz der Lightning Wallet frei zu definieren.",
@ -284,6 +290,7 @@
"language": "Sprache",
"last_updated": "Zuletzt aktualisiert",
"language_isRTL": "BlueWallet zur Aktivierung der Änderung der Schriftrichtung neu starten.",
"license": "Lizenz",
"lightning_error_lndhub_uri": "Keine gültige LndHub URI",
"lightning_saved": "Deine Änderungen wurden gespeichert.",
"lightning_settings": "Lightning-Einstellungen",
@ -336,7 +343,6 @@
"cpfp_title": "TRX-Gebühr erhöhen (CPFP)",
"details_balance_hide": "Guthaben verbergen",
"details_balance_show": "Guthaben zeigen",
"details_block": "Blockhöhe",
"details_copy": "Kopieren",
"details_copy_amount": "Betrag kopieren",
"details_copy_block_explorer_link": "Block-Explorer Link kopieren",
@ -347,7 +353,7 @@
"details_outputs": "Ausgänge",
"date": "Datum",
"details_received": "Empfangen",
"transaction_note_saved": "Transaktionsbezeichnung erfolgreich gespeichert.",
"transaction_saved": "Gespeichert",
"details_show_in_block_explorer": "Im Block-Explorer zeigen",
"details_title": "Transaktion",
"details_to": "Ausgang",
@ -368,6 +374,8 @@
"status_cancel": "Transaktion abbrechen",
"transactions_count": "Anzahl Transaktionen",
"txid": "Transaktions-ID",
"from": "Von: {counterparty}",
"to": "Zu: {counterparty}",
"updating": "Aktualisiere...."
},
"wallets": {
@ -398,6 +406,7 @@
"details_delete": "Löschen",
"details_delete_wallet": "Wallet löschen",
"details_derivation_path": "Ableitungspfad",
"details_display": "Auf der Startseite anzeigen",
"details_export_backup": "Exportieren / Backup",
"details_export_history": "Verlauf als CSV exportieren",
"details_master_fingerprint": "Fingerabdruckkennung",
@ -444,9 +453,9 @@
"list_empty_txs2": "Beginne mit deinem Wallet.",
"list_empty_txs2_lightning": "\nDrücke zum Starten «Beträge verwalten», um das Wallet aufzuladen.",
"list_latest_transaction": "Letzte Transaktion",
"list_ln_browser": "LApp Browser",
"list_long_choose": "Foto auswählen",
"list_long_clipboard": "Aus der Zwischenablage kopieren",
"import_file": "Datei importieren",
"list_long_scan": "QR Code scannen",
"list_title": "Wallets",
"list_tryagain": "Nochmal versuchen",
@ -462,7 +471,8 @@
"warning_do_not_disclose": "Warnung! Nicht veröffentlichen",
"add_ln_wallet_first": "Bitte zuerst ein Lightning-Wallet hinzufügen.",
"identity_pubkey": "Pubkey-Identität",
"xpub_title": "Wallet xPub"
"xpub_title": "Wallet xPub",
"search_wallets": "Wallets suchen"
},
"multisig": {
"multisig_vault": "Tresor",
@ -475,7 +485,10 @@
"fee_btc": "{number} BTC",
"confirm": "Bestätigen",
"header": "Senden",
"share": "Teilen",
"view": "Anzeigen",
"shared_key_detected": "Geteilte Mitsignierer",
"shared_key_detected_question": "Ein Mitsignierer wurde mit Ihnen geteilt. Diesen jetzt importierten?",
"manage_keys": "Schlüssel verwalten",
"how_many_signatures_can_bluewallet_make": "Anzahl Signaturen durch BlueWallet",
"signatures_required_to_spend": "Benötigte Signaturen {number}",
@ -510,6 +523,7 @@
"i_have_mnemonics": "Seed des Schlüssels importieren",
"type_your_mnemonics": "Seed zum Import deines Tresorschlüssels eingeben",
"this_is_cosigners_xpub": "Dies ist der xPub für Mitsigierer zum Import in ein anderes Wallet. Er kann sicher mit anderen geteilt werden.",
"this_is_cosigners_xpub_airdrop": "Zur AirDrop-Freigabe müssen alle Empfänger auf dem Koordinierungsbildschirm sein.",
"wallet_key_created": "Dein Tresorschlüssel wurde erstellt. Nimm dir Zeit ein sicheres Backup des mnemonischen Seeds herzustellen. ",
"are_you_sure_seed_will_be_lost": "Bist du sicher? Dein mnemonischer Seed ist ohne Backup verloren!",
"forget_this_seed": "Seed vergessen und xPub verwenden.",
@ -542,6 +556,12 @@
"no_wallet_owns_address": "Keines der verfügbaren Wallet besitzt die eingegebene Adresse.",
"view_qrcode": "QR-Code anzeigen"
},
"autofill_word": {
"title": "Das letzte mnemonische Wort generieren",
"enter": "Die unvollendete mnemonische Phrase eingeben",
"generate_word": "Erzeuge das letzte Word",
"error": "Die Eingabe ist keine unvollendete 11- oder 23 Wort Phrase. Bitte erneut versuchen."
},
"cc": {
"change": "Ändern",
"coins_selected": "Anz. gewählte Münzen ({number})",
@ -562,6 +582,8 @@
"sats": "sats"
},
"addresses": {
"copy_private_key": "Privaten Schlüssel kopieren",
"sensitive_private_key": "Warnung: Private Schlüssel sind gefahrvoll. Weiterfahren?",
"sign_title": "Meldung signieren/verifizieren",
"sign_help": "Auf einer Bitcoin-Adresse basierende, kryptografische Signatur erstellen oder verifizieren.",
"sign_sign": "Signieren",
@ -594,10 +616,23 @@
"authenticate": "Authentifizieren"
},
"bip47": {
"payment_code": "Bezahlcode",
"payment_codes_list": "Bezahlcodeliste",
"who_can_pay_me": "Wer kann mich bezahlen:",
"payment_code": "Zahlungscode",
"contacts": "Kontakte",
"purpose": "Wiederverwendbarer und teilbarer Code (BIP47)",
"not_found": "Bezahlcode nicht gefunden"
"pay_this_contact": "An diesen Kontakt zahlen",
"rename_contact": "Kontakt umbenennen",
"copy_payment_code": "Zahlungscode kopieren",
"copied": "Kopiert",
"rename": "Umbenennen",
"provide_name": "Neuer Kontaktnahme eingeben",
"add_contact": "Kontakt hinzufügen",
"provide_payment_code": "Zahlungscode eingeben",
"invalid_pc": "Ungültiger Zahlungscode",
"notification_tx_unconfirmed": "Benachrichtigungstransaktion noch unbestätigt. Bitte warten",
"failed_create_notif_tx": "On-Chain Transaktion konnte nicht in erstellt werden",
"onchain_tx_needed": "On-Chain Transaktion benötigt.",
"notif_tx_sent" : "Benachrichtigungstransaktion ist gesendet. Auf Bestätigung warten.",
"notif_tx": "Benachrichtigungstransaktion",
"not_found": "Zahlungscode nicht gefunden"
}
}

View file

@ -384,11 +384,25 @@
"symbol": "Bs.",
"country": "Venezuela (Venezuelan Bolívar Soberano)"
},
"XAF": {
"endPointKey": "XAF",
"locale": "fr-CF",
"source": "CoinDesk",
"symbol": "Fr",
"country": "Central African Republic (Central African Franc)"
},
"ZAR": {
"endPointKey": "ZAR",
"locale": "en-ZA",
"source": "CoinGecko",
"symbol": "R",
"country": "South Africa (South African Rand)"
},
"GHS": {
"endPointKey": "GHS",
"locale": "en-GH",
"source": "CoinDesk",
"symbol": "₵",
"country": "Ghana (Ghanaian Cedi)"
}
}

View file

@ -1,5 +1,4 @@
import { StackActions } from '@react-navigation/native';
import { NativeStackNavigationOptions, createNativeStackNavigator } from '@react-navigation/native-stack';
import { NativeStackNavigationOptions } from '@react-navigation/native-stack';
import React, { useMemo } from 'react';
import { I18nManager, Platform, TouchableOpacity } from 'react-native';
import { Icon } from 'react-native-elements';
@ -21,7 +20,7 @@ import Broadcast from '../screen/send/Broadcast';
import IsItMyAddress from '../screen/send/isItMyAddress';
import Success from '../screen/send/success';
import CPFP from '../screen/transactions/CPFP';
import TransactionDetails from '../screen/transactions/details';
import TransactionDetails from '../screen/transactions/TransactionDetails';
import RBFBumpFee from '../screen/transactions/RBFBumpFee';
import RBFCancel from '../screen/transactions/RBFCancel';
import TransactionStatus from '../screen/transactions/TransactionStatus';
@ -32,7 +31,13 @@ import LdkViewLogs from '../screen/wallets/ldkViewLogs';
import SelectWallet from '../screen/wallets/selectWallet';
import WalletTransactions from '../screen/wallets/transactions';
import WalletsList from '../screen/wallets/WalletsList';
import { NavigationDefaultOptions, NavigationDefaultOptionsForDesktop, NavigationFormModalOptions, StatusBarLightOptions } from './';
import {
NavigationDefaultOptions,
NavigationDefaultOptionsForDesktop,
NavigationFormModalOptions,
StatusBarLightOptions,
DetailViewStack,
} from './index'; // Importing the navigator
import AddWalletStack from './AddWalletStack';
import AztecoRedeemStackRoot from './AztecoRedeemStack';
import ExportMultisigCoordinationSetupStackRoot from './ExportMultisigCoordinationSetupStack';
@ -67,9 +72,8 @@ import SignVerifyStackRoot from './SignVerifyStack';
import ViewEditMultisigCosignersStackRoot from './ViewEditMultisigCosignersStack';
import WalletExportStack from './WalletExportStack';
import WalletXpubStackRoot from './WalletXpubStack';
import { DetailViewStackParamList } from './DetailViewStackParamList';
import { StackActions } from '@react-navigation/native';
const DetailViewRoot = createNativeStackNavigator<DetailViewStackParamList>();
const DetailViewStackScreensStack = () => {
const theme = useTheme();
const navigation = useExtendedNavigation();
@ -107,17 +111,17 @@ const DetailViewStackScreensStack = () => {
const walletListScreenOptions = useWalletListScreenOptions;
return (
<DetailViewRoot.Navigator
<DetailViewStack.Navigator
initialRouteName="WalletsList"
screenOptions={{ headerShadowVisible: false, animationTypeForReplace: 'push' }}
>
<DetailViewRoot.Screen name="WalletsList" component={WalletsList} options={navigationStyle(walletListScreenOptions)(theme)} />
<DetailViewRoot.Screen
<DetailViewStack.Screen name="WalletsList" component={WalletsList} options={navigationStyle(walletListScreenOptions)(theme)} />
<DetailViewStack.Screen
name="WalletTransactions"
component={WalletTransactions}
options={WalletTransactions.navigationOptions(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="LDKOpenChannelRoot"
component={LDKOpenChannelRoot}
options={navigationStyle({
@ -130,8 +134,8 @@ const DetailViewStackScreensStack = () => {
closeButtonFunc: popToTop,
})(theme)}
/>
<DetailViewRoot.Screen name="LdkInfo" component={LdkInfo} options={LdkInfo.navigationOptions(theme)} />
<DetailViewRoot.Screen
<DetailViewStack.Screen name="LdkInfo" component={LdkInfo} options={LdkInfo.navigationOptions(theme)} />
<DetailViewStack.Screen
name="WalletDetails"
component={WalletDetails}
options={navigationStyle({
@ -140,13 +144,13 @@ const DetailViewStackScreensStack = () => {
headerRight: () => SaveButton,
})(theme)}
/>
<DetailViewRoot.Screen name="LdkViewLogs" component={LdkViewLogs} options={LdkViewLogs.navigationOptions(theme)} />
<DetailViewRoot.Screen
<DetailViewStack.Screen name="LdkViewLogs" component={LdkViewLogs} options={LdkViewLogs.navigationOptions(theme)} />
<DetailViewStack.Screen
name="TransactionDetails"
component={TransactionDetails}
options={TransactionDetails.navigationOptions(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="TransactionStatus"
component={TransactionStatus}
initialParams={{
@ -162,16 +166,16 @@ const DetailViewStackScreensStack = () => {
headerRight: () => SaveButton,
})(theme)}
/>
<DetailViewRoot.Screen name="CPFP" component={CPFP} options={CPFP.navigationOptions(theme)} />
<DetailViewRoot.Screen name="RBFBumpFee" component={RBFBumpFee} options={RBFBumpFee.navigationOptions(theme)} />
<DetailViewRoot.Screen name="RBFCancel" component={RBFCancel} options={RBFCancel.navigationOptions(theme)} />
<DetailViewStack.Screen name="CPFP" component={CPFP} options={CPFP.navigationOptions(theme)} />
<DetailViewStack.Screen name="RBFBumpFee" component={RBFBumpFee} options={RBFBumpFee.navigationOptions(theme)} />
<DetailViewStack.Screen name="RBFCancel" component={RBFCancel} options={RBFCancel.navigationOptions(theme)} />
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="SelectWallet"
component={SelectWallet}
options={navigationStyle({ title: loc.wallets.select_wallet })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="LNDViewInvoice"
component={LNDViewInvoice}
options={navigationStyle({
@ -182,25 +186,25 @@ const DetailViewStackScreensStack = () => {
},
})(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="LNDViewAdditionalInvoiceInformation"
component={LNDViewAdditionalInvoiceInformation}
options={navigationStyle({ title: loc.lndViewInvoice.additional_info })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="LNDViewAdditionalInvoicePreImage"
component={LNDViewAdditionalInvoicePreImage}
options={navigationStyle({ title: loc.lndViewInvoice.additional_info })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="Broadcast"
component={Broadcast}
options={navigationStyle({ title: loc.send.create_broadcast })(theme)}
/>
<DetailViewRoot.Screen name="IsItMyAddress" component={IsItMyAddress} options={IsItMyAddress.navigationOptions(theme)} />
<DetailViewRoot.Screen name="GenerateWord" component={GenerateWord} options={GenerateWord.navigationOptions(theme)} />
<DetailViewRoot.Screen
<DetailViewStack.Screen name="IsItMyAddress" component={IsItMyAddress} options={IsItMyAddress.navigationOptions(theme)} />
<DetailViewStack.Screen name="GenerateWord" component={GenerateWord} options={GenerateWord.navigationOptions(theme)} />
<DetailViewStack.Screen
name="LnurlPay"
component={LnurlPay}
options={navigationStyle({
@ -209,7 +213,7 @@ const DetailViewStackScreensStack = () => {
closeButtonFunc: popToTop,
})(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="LnurlPaySuccess"
component={LnurlPaySuccess}
options={navigationStyle({
@ -220,8 +224,8 @@ const DetailViewStackScreensStack = () => {
closeButtonFunc: popToTop,
})(theme)}
/>
<DetailViewRoot.Screen name="LnurlAuth" component={LnurlAuth} options={LnurlAuth.navigationOptions(theme)} />
<DetailViewRoot.Screen
<DetailViewStack.Screen name="LnurlAuth" component={LnurlAuth} options={LnurlAuth.navigationOptions(theme)} />
<DetailViewStack.Screen
name="Success"
component={Success}
options={{
@ -229,29 +233,29 @@ const DetailViewStackScreensStack = () => {
gestureEnabled: false,
}}
/>
<DetailViewRoot.Screen name="WalletAddresses" component={WalletAddresses} options={WalletAddresses.navigationOptions(theme)} />
<DetailViewStack.Screen name="WalletAddresses" component={WalletAddresses} options={WalletAddresses.navigationOptions(theme)} />
<DetailViewRoot.Screen name="AddWalletRoot" component={AddWalletStack} options={NavigationFormModalOptions} />
<DetailViewRoot.Screen
<DetailViewStack.Screen name="AddWalletRoot" component={AddWalletStack} options={NavigationFormModalOptions} />
<DetailViewStack.Screen
name="SendDetailsRoot"
component={SendDetailsStack}
options={isDesktop ? NavigationDefaultOptionsForDesktop : NavigationDefaultOptions}
/>
<DetailViewRoot.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={NavigationDefaultOptions} />
<DetailViewRoot.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={NavigationDefaultOptions} />
<DetailViewRoot.Screen name="AztecoRedeemRoot" component={AztecoRedeemStackRoot} options={NavigationDefaultOptions} />
<DetailViewStack.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={NavigationDefaultOptions} />
<DetailViewStack.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={NavigationDefaultOptions} />
<DetailViewStack.Screen name="AztecoRedeemRoot" component={AztecoRedeemStackRoot} options={NavigationDefaultOptions} />
{/* screens */}
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="WalletExportRoot"
component={WalletExportStack}
options={{ ...NavigationDefaultOptions, ...StatusBarLightOptions }}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="ExportMultisigCoordinationSetupRoot"
component={ExportMultisigCoordinationSetupStackRoot}
options={NavigationDefaultOptions}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="Settings"
component={SettingsComponent}
options={navigationStyle({
@ -262,96 +266,96 @@ const DetailViewStackScreensStack = () => {
animationTypeForReplace: 'push',
})(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="Currency"
component={CurrencyComponent}
options={navigationStyle({ title: loc.settings.currency })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="GeneralSettings"
component={GeneralSettingsComponent}
options={navigationStyle({ title: loc.settings.general })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="PlausibleDeniability"
component={PlausibleDeniabilityComponent}
options={navigationStyle({ title: loc.plausibledeniability.title })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="Licensing"
component={LicensingComponent}
options={navigationStyle({ title: loc.settings.license })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="NetworkSettings"
component={NetworkSettingsComponent}
options={navigationStyle({ title: loc.settings.network })(theme)}
/>
<DetailViewRoot.Screen name="About" component={AboutComponent} options={navigationStyle({ title: loc.settings.about })(theme)} />
<DetailViewRoot.Screen
<DetailViewStack.Screen name="About" component={AboutComponent} options={navigationStyle({ title: loc.settings.about })(theme)} />
<DetailViewStack.Screen
name="DefaultView"
component={DefaultViewComponent}
options={navigationStyle({ title: loc.settings.default_title })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="ElectrumSettings"
component={ElectrumSettingsComponent}
options={navigationStyle({ title: loc.settings.electrum_settings_server })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="EncryptStorage"
component={EncryptStorageComponent}
options={navigationStyle({ title: loc.settings.encrypt_title })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="Language"
component={LanguageComponent}
options={navigationStyle({ title: loc.settings.language })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="LightningSettings"
component={LightningSettingsComponent}
options={navigationStyle({ title: loc.settings.lightning_settings })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="NotificationSettings"
component={NotificationSettingsComponent}
options={navigationStyle({ title: loc.settings.notifications })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="SelfTest"
component={SelfTestComponent}
options={navigationStyle({ title: loc.settings.selfTest })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="ReleaseNotes"
component={ReleaseNotesComponent}
options={navigationStyle({ title: loc.settings.about_release_notes })(theme)}
/>
<DetailViewRoot.Screen name="Tools" component={ToolsComponent} options={navigationStyle({ title: loc.settings.tools })(theme)} />
<DetailViewRoot.Screen
<DetailViewStack.Screen name="Tools" component={ToolsComponent} options={navigationStyle({ title: loc.settings.tools })(theme)} />
<DetailViewStack.Screen
name="SettingsPrivacy"
component={SettingsPrivacyComponent}
options={navigationStyle({ headerLargeTitle: true, title: loc.settings.privacy })(theme)}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="ViewEditMultisigCosignersRoot"
component={ViewEditMultisigCosignersStackRoot}
options={{ ...NavigationDefaultOptions, ...StatusBarLightOptions, gestureEnabled: false, fullScreenGestureEnabled: false }}
initialParams={{ walletID: undefined, cosigners: undefined }}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="WalletXpubRoot"
component={WalletXpubStackRoot}
options={{ ...NavigationDefaultOptions, ...StatusBarLightOptions }}
/>
<DetailViewRoot.Screen
<DetailViewStack.Screen
name="SignVerifyRoot"
component={SignVerifyStackRoot}
options={{ ...NavigationDefaultOptions, ...StatusBarLightOptions }}
/>
<DetailViewRoot.Screen name="ReceiveDetailsRoot" component={ReceiveDetailsStackRoot} options={NavigationDefaultOptions} />
<DetailViewRoot.Screen
<DetailViewStack.Screen name="ReceiveDetailsRoot" component={ReceiveDetailsStackRoot} options={NavigationDefaultOptions} />
<DetailViewStack.Screen
name="ScanQRCodeRoot"
component={ScanQRCodeStackRoot}
options={{
@ -359,23 +363,10 @@ const DetailViewStackScreensStack = () => {
presentation: 'fullScreenModal',
statusBarHidden: true,
}}
initialParams={{
isLoading: false,
cameraStatusGranted: undefined,
backdoorPressed: undefined,
launchedBy: undefined,
urTotal: undefined,
urHave: undefined,
backdoorText: '',
onDismiss: undefined,
showFileImportButton: true,
backdoorVisible: false,
animatedQRCodeData: {},
}}
/>
<DetailViewRoot.Screen name="PaymentCodeRoot" component={PaymentCodeStackRoot} options={NavigationDefaultOptions} />
<DetailViewRoot.Screen
<DetailViewStack.Screen name="PaymentCodeRoot" component={PaymentCodeStackRoot} options={NavigationDefaultOptions} />
<DetailViewStack.Screen
name="ReorderWallets"
component={ReorderWalletsStackRoot}
options={{
@ -384,7 +375,7 @@ const DetailViewStackScreensStack = () => {
presentation: 'modal',
}}
/>
</DetailViewRoot.Navigator>
</DetailViewStack.Navigator>
);
};

View file

@ -1,4 +1,7 @@
import { LightningTransaction } from '../class/wallets/types';
export type DetailViewStackParamList = {
UnlockWithScreen: undefined;
WalletsList: undefined;
WalletTransactions: { walletID: string; walletType: string };
LDKOpenChannelRoot: undefined;
@ -11,21 +14,32 @@ export type DetailViewStackParamList = {
RBFBumpFee: { transactionId: string };
RBFCancel: { transactionId: string };
SelectWallet: undefined;
LNDViewInvoice: { invoiceId: string };
LNDViewInvoice: { invoice: LightningTransaction; walletID: string };
LNDViewAdditionalInvoiceInformation: { invoiceId: string };
LNDViewAdditionalInvoicePreImage: { invoiceId: string };
Broadcast: undefined;
IsItMyAddress: undefined;
GenerateWord: undefined;
LnurlPay: undefined;
LnurlPaySuccess: undefined;
LnurlPaySuccess: {
paymentHash: string;
justPaid: boolean;
fromWalletID: string;
};
LnurlAuth: undefined;
Success: undefined;
WalletAddresses: { walletID: string };
AddWalletRoot: undefined;
SendDetailsRoot: undefined;
LNDCreateInvoiceRoot: undefined;
ScanLndInvoiceRoot: undefined;
ScanLndInvoiceRoot: {
screen: string;
params: {
paymentHash: string;
fromWalletID: string;
justPaid: boolean;
};
};
AztecoRedeemRoot: undefined;
WalletExportRoot: undefined;
ExportMultisigCoordinationSetupRoot: undefined;
@ -40,7 +54,9 @@ export type DetailViewStackParamList = {
ElectrumSettings: undefined;
EncryptStorage: undefined;
Language: undefined;
LightningSettings: undefined;
LightningSettings: {
url?: string;
};
NotificationSettings: undefined;
SelfTest: undefined;
ReleaseNotes: undefined;
@ -48,20 +64,35 @@ export type DetailViewStackParamList = {
SettingsPrivacy: undefined;
ViewEditMultisigCosignersRoot: { walletID: string; cosigners: string[] };
WalletXpubRoot: undefined;
SignVerifyRoot: undefined;
ReceiveDetailsRoot: undefined;
SignVerifyRoot: {
screen: 'SignVerify';
params: {
walletID: string;
address: string;
};
};
ReceiveDetailsRoot: {
screen: 'ReceiveDetails';
params: {
walletID: string;
address: string;
};
};
ScanQRCodeRoot: {
isLoading?: boolean;
cameraStatusGranted?: boolean;
backdoorPressed?: boolean;
launchedBy?: string;
urTotal?: number;
urHave?: number;
backdoorText?: string;
onDismiss?: () => void;
showFileImportButton?: boolean;
backdoorVisible?: boolean;
animatedQRCodeData?: Record<string, any>;
screen: string;
params: {
isLoading: false;
cameraStatusGranted?: boolean;
backdoorPressed?: boolean;
launchedBy?: string;
urTotal?: number;
urHave?: number;
backdoorText?: string;
onDismiss?: () => void;
showFileImportButton: true;
backdoorVisible?: boolean;
animatedQRCodeData?: Record<string, any>;
};
};
PaymentCodeRoot: undefined;
ReorderWallets: undefined;

View file

@ -11,7 +11,7 @@ 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 EncryptStorage = lazy(() => import('../screen/settings/encryptStorage'));
const EncryptStorage = lazy(() => import('../screen/settings/EncryptStorage'));
const LightningSettings = lazy(() => import('../screen/settings/lightningSettings'));
const NotificationSettings = lazy(() => import('../screen/settings/notificationSettings'));
const SelfTest = lazy(() => import('../screen/settings/SelfTest'));

View file

@ -5,6 +5,7 @@ import { isHandset } from '../blue_modules/environment';
import { useStorage } from '../blue_modules/storage-context';
import UnlockWith from '../screen/UnlockWith';
import { LazyLoadingIndicator } from './LazyLoadingIndicator';
import { DetailViewStackParamList } from './DetailViewStackParamList';
const DetailViewScreensStack = lazy(() => import('./DetailViewScreensStack'));
const DrawerRoot = lazy(() => import('./DrawerRoot'));
@ -21,15 +22,16 @@ export const NavigationFormModalOptions: NativeStackNavigationOptions = {
export const NavigationDefaultOptionsForDesktop: NativeStackNavigationOptions = { headerShown: false, presentation: 'fullScreenModal' };
export const StatusBarLightOptions: NativeStackNavigationOptions = { statusBarStyle: 'light' };
const UnlockStack = createNativeStackNavigator();
const DetailViewStack = createNativeStackNavigator<DetailViewStackParamList>();
const UnlockRoot = () => {
return (
<UnlockStack.Navigator screenOptions={{ headerShown: false, animationTypeForReplace: 'push' }}>
<UnlockStack.Screen name="UnlockWithScreen" component={UnlockWith} />
</UnlockStack.Navigator>
<DetailViewStack.Navigator screenOptions={{ headerShown: false, animationTypeForReplace: 'push' }}>
<DetailViewStack.Screen name="UnlockWithScreen" component={UnlockWith} />
</DetailViewStack.Navigator>
);
};
const MainRoot = () => {
const { walletsInitialized } = useStorage();
@ -51,3 +53,4 @@ const MainRoot = () => {
};
export default MainRoot;
export { DetailViewStack }; // Exporting the navigator to use it in DetailViewScreensStack

View file

@ -1,6 +1,6 @@
import { useNavigation, useRoute } from '@react-navigation/native';
import * as bitcoin from 'bitcoinjs-lib';
import React, { useState } from 'react';
import { useRoute } from '@react-navigation/native';
import * as bitcoin from 'bitcoinjs-lib';
import { ActivityIndicator, Keyboard, KeyboardAvoidingView, Linking, Platform, StyleSheet, TextInput, View } from 'react-native';
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
@ -37,7 +37,6 @@ interface SuccessScreenProps {
const Broadcast: React.FC = () => {
const { name } = useRoute();
const { navigate } = useNavigation();
const [tx, setTx] = useState<string | undefined>();
const [txHex, setTxHex] = useState<string | undefined>();
const { colors } = useTheme();
@ -83,7 +82,7 @@ const Broadcast: React.FC = () => {
};
const handleQRScan = async () => {
const scannedData = await scanQrHelper(navigate, name);
const scannedData = await scanQrHelper(name);
if (!scannedData) return;
if (scannedData.indexOf('+') === -1 && scannedData.indexOf('=') === -1 && scannedData.indexOf('=') === -1) {

View file

@ -857,7 +857,7 @@ const SendDetails = () => {
setIsLoading(true);
setOptionsVisible(false);
await new Promise(resolve => setTimeout(resolve, 100)); // sleep for animations
const scannedData = await scanQrHelper(navigation.navigate, name);
const scannedData = await scanQrHelper(name);
if (!scannedData) return setIsLoading(false);
let tx;

View file

@ -60,7 +60,7 @@ const PsbtMultisigQRCode = () => {
};
const openScanner = async () => {
const scanned = await scanQrHelper(navigate, name, true);
const scanned = await scanQrHelper(name, true);
onBarScanned({ data: scanned });
};

View file

@ -1,7 +1,7 @@
import dayjs from 'dayjs';
import calendar from 'dayjs/plugin/calendar';
import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { FlatList, NativeSyntheticEvent, StyleSheet, View } from 'react-native';
import { FlatList, NativeSyntheticEvent, StyleSheet, View, LayoutAnimation, UIManager, Platform } from 'react-native';
import {
CurrencyRate,
@ -18,30 +18,30 @@ import { useTheme } from '../../components/themes';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import loc from '../../loc';
import { FiatUnit, FiatUnitSource, FiatUnitType, getFiatRate } from '../../models/fiatUnit';
import useDebounce from '../../hooks/useDebounce';
dayjs.extend(calendar);
// Enable LayoutAnimation for Android
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
const Currency: React.FC = () => {
const { setPreferredFiatCurrencyStorage } = useSettings();
const [isSavingNewPreferredCurrency, setIsSavingNewPreferredCurrency] = useState(false);
const [isSavingNewPreferredCurrency, setIsSavingNewPreferredCurrency] = useState<FiatUnitType | undefined>();
const [selectedCurrency, setSelectedCurrency] = useState<FiatUnitType>(FiatUnit.USD);
const [currencyRate, setCurrencyRate] = useState<CurrencyRate>({ LastUpdated: null, Rate: null });
const [isSearchFocused, setIsSearchFocused] = useState(false);
const { colors } = useTheme();
const { setOptions } = useExtendedNavigation();
const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 300);
const data = useMemo(
() =>
Object.values(FiatUnit).filter(
item =>
item.endPointKey.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
item.country.toLowerCase().includes(debouncedSearch.toLowerCase()),
),
[debouncedSearch],
);
const data = useMemo(() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
return Object.values(FiatUnit).filter(
item => item.endPointKey.toLowerCase().includes(search.toLowerCase()) || item.country.toLowerCase().includes(search.toLowerCase()),
);
}, [search]);
const stylesHook = StyleSheet.create({
flex: {
@ -80,13 +80,14 @@ const Currency: React.FC = () => {
const renderItem = ({ item }: { item: FiatUnitType }) => (
<ListItem
disabled={isSavingNewPreferredCurrency || selectedCurrency.endPointKey === item.endPointKey}
disabled={isSavingNewPreferredCurrency === item || selectedCurrency.endPointKey === item.endPointKey}
title={`${item.endPointKey} (${item.symbol})`}
containerStyle={StyleSheet.flatten([styles.flex, stylesHook.flex, { minHeight: 60 }])}
checkmark={selectedCurrency.endPointKey === item.endPointKey}
isLoading={isSavingNewPreferredCurrency && selectedCurrency.endPointKey === item.endPointKey}
subtitle={item.country}
onPress={async () => {
setIsSavingNewPreferredCurrency(true);
setIsSavingNewPreferredCurrency(item);
try {
await getFiatRate(item.endPointKey);
await setPreferredCurrency(item);
@ -100,7 +101,7 @@ const Currency: React.FC = () => {
message: error.message ? `${loc.settings.currency_fetch_error}: ${error.message}` : loc.settings.currency_fetch_error,
});
} finally {
setIsSavingNewPreferredCurrency(false);
setIsSavingNewPreferredCurrency(undefined);
}
}}
/>

View file

@ -0,0 +1,272 @@
import React, { useCallback, useEffect, useReducer } from 'react';
import { Alert, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
import { StackActions } from '@react-navigation/native';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { useStorage } from '../../blue_modules/storage-context';
import { BlueCard, BlueSpacing20, BlueText } from '../../BlueComponents';
import presentAlert from '../../components/Alert';
import ListItem from '../../components/ListItem';
import { useTheme } from '../../components/themes';
import prompt from '../../helpers/prompt';
import { useBiometrics } from '../../hooks/useBiometrics';
import loc from '../../loc';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
enum ActionType {
SetLoading = 'SET_LOADING',
SetStorageEncryptedSwitch = 'SET_STORAGE_ENCRYPTED_SWITCH',
SetDeviceBiometricCapable = 'SET_DEVICE_BIOMETRIC_CAPABLE',
SetCurrentLoadingSwitch = 'SET_CURRENT_LOADING_SWITCH',
}
interface State {
isLoading: boolean;
storageIsEncryptedSwitchEnabled: boolean;
deviceBiometricCapable: boolean;
currentLoadingSwitch: string | null;
}
interface Action {
type: ActionType;
payload?: any;
}
const initialState: State = {
isLoading: true,
storageIsEncryptedSwitchEnabled: false,
deviceBiometricCapable: false,
currentLoadingSwitch: null,
};
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case ActionType.SetLoading:
return { ...state, isLoading: action.payload };
case ActionType.SetStorageEncryptedSwitch:
return { ...state, storageIsEncryptedSwitchEnabled: action.payload };
case ActionType.SetDeviceBiometricCapable:
return { ...state, deviceBiometricCapable: action.payload };
case ActionType.SetCurrentLoadingSwitch:
return { ...state, currentLoadingSwitch: action.payload };
default:
return state;
}
};
const EncryptStorage = () => {
const { isStorageEncrypted, encryptStorage, decryptStorage, saveToDisk } = useStorage();
const { isDeviceBiometricCapable, biometricEnabled, setBiometricUseEnabled, deviceBiometricType, unlockWithBiometrics } = useBiometrics();
const [state, dispatch] = useReducer(reducer, initialState);
const { navigate, dispatch: navigationDispatch } = useExtendedNavigation();
const { colors } = useTheme();
const styleHooks = StyleSheet.create({
root: {
backgroundColor: colors.background,
},
headerText: {
color: colors.foregroundColor,
},
});
const initializeState = useCallback(async () => {
const isStorageEncryptedSwitchEnabled = await isStorageEncrypted();
const isDeviceBiometricCapableSync = await isDeviceBiometricCapable();
dispatch({ type: ActionType.SetStorageEncryptedSwitch, payload: isStorageEncryptedSwitchEnabled });
dispatch({ type: ActionType.SetDeviceBiometricCapable, payload: isDeviceBiometricCapableSync });
dispatch({ type: ActionType.SetLoading, payload: false });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
initializeState();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const popToTop = () => {
navigationDispatch(StackActions.popToTop());
};
const handleDecryptStorage = async () => {
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: 'decrypt' });
const password = await prompt(loc.settings.password, loc._.storage_is_encrypted).catch(() => {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
});
if (!password) {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
return;
}
try {
await decryptStorage(password);
await saveToDisk();
popToTop();
} catch (e) {
if (password) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: loc._.bad_password });
}
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
dispatch({ type: ActionType.SetStorageEncryptedSwitch, payload: await isStorageEncrypted() });
}
};
const onEncryptStorageSwitch = async (value: boolean) => {
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: 'encrypt' });
dispatch({ type: ActionType.SetLoading, payload: true });
if (value) {
let p1 = await prompt(loc.settings.password, loc.settings.password_explain).catch(() => {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
p1 = undefined;
});
if (!p1) {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
return;
}
const p2 = await prompt(loc.settings.password, loc.settings.retype_password).catch(() => {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
});
if (p1 === p2) {
await encryptStorage(p1);
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
dispatch({ type: ActionType.SetStorageEncryptedSwitch, payload: await isStorageEncrypted() });
saveToDisk();
} else {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: loc.settings.passwords_do_not_match });
}
} else {
Alert.alert(
loc.settings.encrypt_decrypt,
loc.settings.encrypt_decrypt_q,
[
{
text: loc._.cancel,
style: 'cancel',
onPress: () => {
dispatch({ type: ActionType.SetLoading, payload: false });
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
},
},
{
text: loc._.ok,
style: 'destructive',
onPress: handleDecryptStorage,
},
],
{ cancelable: false },
);
}
};
const onUseBiometricSwitch = async (value: boolean) => {
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: 'biometric' });
if (await unlockWithBiometrics()) {
setBiometricUseEnabled(value);
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
} else {
dispatch({ type: ActionType.SetCurrentLoadingSwitch, payload: null });
}
};
const navigateToPlausibleDeniability = () => {
navigate('PlausibleDeniability');
};
const renderPasscodeExplanation = () => {
let isCapable = true;
if (Platform.OS === 'android') {
if (Platform.Version < 30) {
isCapable = false;
}
}
return isCapable ? (
<>
<BlueText />
<BlueText>{loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType })}</BlueText>
</>
) : null;
};
return (
<ScrollView
contentContainerStyle={[styles.root, styleHooks.root]}
automaticallyAdjustContentInsets
contentInsetAdjustmentBehavior="automatic"
>
<View style={styles.paddingTop} />
{state.deviceBiometricCapable && (
<>
<Text adjustsFontSizeToFit style={[styles.headerText, styleHooks.headerText]}>
{loc.settings.biometrics}
</Text>
<ListItem
title={loc.formatString(loc.settings.encrypt_use, { type: deviceBiometricType })}
Component={TouchableWithoutFeedback}
switch={{ value: biometricEnabled, onValueChange: onUseBiometricSwitch, disabled: state.currentLoadingSwitch !== null }}
isLoading={state.currentLoadingSwitch === 'biometric' && state.isLoading}
containerStyle={[styles.row, styleHooks.root]}
/>
<BlueCard>
<BlueText>{loc.formatString(loc.settings.encrypt_use_expl, { type: deviceBiometricType })}</BlueText>
{renderPasscodeExplanation()}
</BlueCard>
<BlueSpacing20 />
</>
)}
<Text adjustsFontSizeToFit style={[styles.headerText, styleHooks.headerText]}>
{loc.settings.encrypt_tstorage}
</Text>
<ListItem
testID="EncyptedAndPasswordProtected"
hideChevron
title={loc.settings.encrypt_enc_and_pass}
Component={TouchableWithoutFeedback}
switch={{
onValueChange: onEncryptStorageSwitch,
value: state.storageIsEncryptedSwitchEnabled,
disabled: state.currentLoadingSwitch !== null,
}}
isLoading={state.currentLoadingSwitch === 'encrypt' && state.isLoading}
containerStyle={[styles.row, styleHooks.root]}
/>
{state.storageIsEncryptedSwitchEnabled && (
<ListItem
onPress={navigateToPlausibleDeniability}
title={loc.settings.plausible_deniability}
chevron
testID="PlausibleDeniabilityButton"
Component={TouchableOpacity}
containerStyle={[styles.row, styleHooks.root]}
/>
)}
</ScrollView>
);
};
const styles = StyleSheet.create({
root: {
flex: 1,
},
paddingTop: { paddingTop: 19 },
headerText: {
fontWeight: 'bold',
fontSize: 30,
marginLeft: 17,
},
row: { minHeight: 60 },
});
export default EncryptStorage;

View file

@ -36,7 +36,6 @@ import ListItem from '../../components/ListItem';
import { BlueCurrentTheme } from '../../components/themes';
import { scanQrHelper } from '../../helpers/scan-qr';
import loc from '../../loc';
import { navigationRef } from '../../NavigationService';
export default class ElectrumSettings extends Component {
static contextType = BlueStorageContext;
@ -225,7 +224,7 @@ export default class ElectrumSettings extends Component {
};
importScan = async () => {
const scanned = await scanQrHelper(navigationRef.navigate, 'ElectrumSettings', true);
const scanned = await scanQrHelper('ElectrumSettings', true);
this.onBarScanned(scanned);
};

View file

@ -1,195 +0,0 @@
import { useNavigation } from '@react-navigation/native';
import React, { useCallback, useEffect, useState } from 'react';
import { Alert, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
import { useStorage } from '../../blue_modules/storage-context';
import { BlueCard, BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents';
import presentAlert from '../../components/Alert';
import ListItem from '../../components/ListItem';
import { useTheme } from '../../components/themes';
import prompt from '../../helpers/prompt';
import { useBiometrics } from '../../hooks/useBiometrics';
import loc from '../../loc';
const EncryptStorage = () => {
const { isStorageEncrypted, encryptStorage, decryptStorage, saveToDisk } = useStorage();
const [isLoading, setIsLoading] = useState(true);
const { isDeviceBiometricCapable, biometricEnabled, setBiometricUseEnabled, deviceBiometricType, unlockWithBiometrics } = useBiometrics();
const [storageIsEncryptedSwitchEnabled, setStorageIsEncryptedSwitchEnabled] = useState(false);
const [deviceBiometricCapable, setDeviceBiometricCapable] = useState(false);
const { navigate, popToTop } = useNavigation();
const { colors } = useTheme();
const styleHooks = StyleSheet.create({
root: {
backgroundColor: colors.background,
},
headerText: {
color: colors.foregroundColor,
},
});
const initialState = useCallback(async () => {
const isStorageEncryptedSwitchEnabled = await isStorageEncrypted();
const isDeviceBiometricCapableSync = await isDeviceBiometricCapable();
setStorageIsEncryptedSwitchEnabled(isStorageEncryptedSwitchEnabled);
setDeviceBiometricCapable(isDeviceBiometricCapableSync);
setIsLoading(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
initialState();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleDecryptStorage = async () => {
const password = await prompt(loc.settings.password, loc._.storage_is_encrypted).catch(() => {
setIsLoading(false);
});
try {
await decryptStorage(password);
await saveToDisk();
popToTop();
} catch (e) {
if (password) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: loc._.bad_password });
}
setIsLoading(false);
setStorageIsEncryptedSwitchEnabled(await isStorageEncrypted());
}
};
const onEncryptStorageSwitch = async value => {
setIsLoading(true);
if (value === true) {
let p1 = await prompt(loc.settings.password, loc.settings.password_explain).catch(() => {
setIsLoading(false);
p1 = undefined;
});
if (!p1) {
setIsLoading(false);
return;
}
const p2 = await prompt(loc.settings.password, loc.settings.retype_password).catch(() => {
setIsLoading(false);
});
if (p1 === p2) {
await encryptStorage(p1);
setIsLoading(false);
setStorageIsEncryptedSwitchEnabled(await isStorageEncrypted());
saveToDisk();
} else {
setIsLoading(false);
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: loc.settings.passwords_do_not_match });
}
} else {
Alert.alert(
loc.settings.encrypt_decrypt,
loc.settings.encrypt_decrypt_q,
[
{
text: loc._.cancel,
style: 'cancel',
onPress: () => setIsLoading(false),
},
{
text: loc._.ok,
style: 'destructive',
onPress: handleDecryptStorage,
},
],
{ cancelable: false },
);
}
};
const onUseBiometricSwitch = async value => {
if (await unlockWithBiometrics()) {
setBiometricUseEnabled(value);
}
};
const navigateToPlausibleDeniability = () => {
navigate('PlausibleDeniability');
};
const renderPasscodeExplanation = () => {
let isCapable = true;
if (Platform.OS === 'android') {
if (Platform.Version < 30) {
isCapable = false;
}
}
return isCapable ? (
<>
<BlueText />
<BlueText>{loc.formatString(loc.settings.biometrics_fail, { type: deviceBiometricType })}</BlueText>
</>
) : null;
};
return isLoading ? (
<ScrollView centerContent>
<BlueLoading />
</ScrollView>
) : (
<ScrollView contentContainerStyle={styles.root} automaticallyAdjustContentInsets contentInsetAdjustmentBehavior="automatic">
<View style={styles.paddingTop} />
{deviceBiometricCapable && (
<>
<Text adjustsFontSizeToFit style={[styles.headerText, styleHooks.headerText]}>
{loc.settings.biometrics}
</Text>
<ListItem
title={loc.formatString(loc.settings.encrypt_use, { type: deviceBiometricType })}
Component={TouchableWithoutFeedback}
switch={{ value: biometricEnabled, onValueChange: onUseBiometricSwitch }}
/>
<BlueCard>
<BlueText>{loc.formatString(loc.settings.encrypt_use_expl, { type: deviceBiometricType })}</BlueText>
{renderPasscodeExplanation()}
</BlueCard>
<BlueSpacing20 />
</>
)}
<Text adjustsFontSizeToFit style={[styles.headerText, styleHooks.headerText]}>
{loc.settings.encrypt_tstorage}
</Text>
<ListItem
testID="EncyptedAndPasswordProtected"
hideChevron
title={loc.settings.encrypt_enc_and_pass}
Component={TouchableWithoutFeedback}
switch={{ onValueChange: onEncryptStorageSwitch, value: storageIsEncryptedSwitchEnabled }}
/>
{storageIsEncryptedSwitchEnabled && (
<ListItem
onPress={navigateToPlausibleDeniability}
title={loc.settings.plausible_deniability}
chevron
testID="PlausibleDeniabilityButton"
Component={TouchableOpacity}
/>
)}
</ScrollView>
);
};
const styles = StyleSheet.create({
root: {
flex: 1,
},
paddingTop: { paddingTop: 19 },
headerText: {
fontWeight: 'bold',
fontSize: 30,
marginLeft: 17,
},
});
export default EncryptStorage;

View file

@ -1,6 +1,6 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import React, { useCallback, useEffect, useState } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { RouteProp, useRoute } from '@react-navigation/native';
import { Alert, I18nManager, Linking, ScrollView, StyleSheet, TextInput, View } from 'react-native';
import { Button as ButtonRNElements } from 'react-native-elements';
@ -11,7 +11,7 @@ import { LightningCustodianWallet } from '../../class/wallets/lightning-custodia
import presentAlert from '../../components/Alert';
import { Button } from '../../components/Button';
import { useTheme } from '../../components/themes';
import { requestCameraAuthorization } from '../../helpers/scan-qr';
import { scanQrHelper } from '../../helpers/scan-qr';
import loc from '../../loc';
const styles = StyleSheet.create({
@ -52,7 +52,6 @@ const LightningSettings: React.FC = () => {
const [URI, setURI] = useState<string>();
const { colors } = useTheme();
const route = useRoute();
const navigation = useNavigation();
const styleHook = StyleSheet.create({
uri: {
borderColor: colors.formBorder,
@ -114,17 +113,11 @@ const LightningSettings: React.FC = () => {
}, [URI]);
const importScan = () => {
requestCameraAuthorization().then(() =>
// @ts-ignore: Address types later
navigation.navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
launchedBy: route.name,
onBarScanned: setLndhubURI,
showFileImportButton: true,
},
}),
);
scanQrHelper(route.name).then(data => {
if (data) {
setLndhubURI(data);
}
});
};
return (

View file

@ -1,5 +1,5 @@
import Clipboard from '@react-native-clipboard/clipboard';
import { RouteProp, useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
import { RouteProp, useFocusEffect, useRoute } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import assert from 'assert';
import dayjs from 'dayjs';
@ -18,6 +18,8 @@ import navigationStyle from '../../components/navigationStyle';
import { useTheme } from '../../components/themes';
import ToolTipMenu from '../../components/TooltipMenu';
import loc from '../../loc';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
interface TransactionDetailsProps {
route: RouteProp<{ params: { hash: string; walletID: string } }, 'params'>;
@ -62,8 +64,10 @@ const toolTipMenuActions = [
},
];
type NavigationProps = NativeStackNavigationProp<DetailViewStackParamList, 'TransactionDetails'>;
const TransactionDetails = () => {
const { setOptions, navigate } = useNavigation();
const { setOptions, navigate } = useExtendedNavigation<NavigationProps>();
const { hash, walletID } = useRoute<TransactionDetailsProps['route']>().params;
const { saveToDisk, txMetadata, counterpartyMetadata, wallets, getTransactions } = useContext(BlueStorageContext);
const [from, setFrom] = useState<string[]>([]);
@ -103,9 +107,9 @@ const TransactionDetails = () => {
}, [tx, txMetadata, memo, counterpartyLabel, paymentCode, saveToDisk, counterpartyMetadata]);
const HeaderRight = useMemo(
() => <HeaderRightButton disabled={isLoading} onPress={handleOnSaveButtonTapped} title={loc.wallets.details_save} />,
() => <HeaderRightButton onPress={handleOnSaveButtonTapped} disabled={false} title={loc.wallets.details_save} />,
[isLoading, handleOnSaveButtonTapped],
[handleOnSaveButtonTapped],
);
useEffect(() => {
@ -204,7 +208,6 @@ const TransactionDetails = () => {
};
const navigateToWallet = (wallet: TWallet) => {
// @ts-ignore idk how to fix it
navigate('WalletTransactions', {
walletID: wallet.getID(),
walletType: wallet.type,

View file

@ -18,7 +18,7 @@ import {
} from 'react-native';
import { Badge, Icon } from 'react-native-elements';
import { isDesktop } from '../../blue_modules/environment';
import { isDesktop, isTablet } from '../../blue_modules/environment';
import { useStorage } from '../../blue_modules/storage-context';
import { encodeUR } from '../../blue_modules/ur';
import {
@ -51,7 +51,6 @@ import { useBiometrics } from '../../hooks/useBiometrics';
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import usePrivacy from '../../hooks/usePrivacy';
import loc from '../../loc';
import * as NavigationService from '../../NavigationService';
import ActionSheet from '../ActionSheet';
const ViewEditMultisigCosigners: React.FC = () => {
@ -192,7 +191,6 @@ const ViewEditMultisigCosigners: React.FC = () => {
await wallet?.fetchBalance();
}
newWallets.push(wallet);
// @ts-ignore wtf
navigate('WalletsList');
setTimeout(() => {
setWalletsWithNewOrder(newWallets);
@ -513,7 +511,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
const scanOrOpenFile = async () => {
setIsProvideMnemonicsModalVisible(false);
const scanned = await scanQrHelper(NavigationService.navigate, route.name, true);
const scanned = await scanQrHelper(route.name, true);
setImportText(String(scanned));
setIsProvideMnemonicsModalVisible(true);
};
@ -530,11 +528,9 @@ const ViewEditMultisigCosigners: React.FC = () => {
};
const renderProvideMnemonicsModal = () => {
// @ts-ignore weird, property exists on type definition. might be some ts bugs
const isPad: boolean = Platform.isPad;
return (
<BottomModal avoidKeyboard isVisible={isProvideMnemonicsModalVisible} onClose={hideProvideMnemonicsModal} coverScreen={false}>
<KeyboardAvoidingView enabled={!isPad} behavior={Platform.OS === 'ios' ? 'position' : 'padding'} keyboardVerticalOffset={120}>
<KeyboardAvoidingView enabled={!isTablet} behavior={Platform.OS === 'ios' ? 'position' : 'padding'} keyboardVerticalOffset={120}>
<View style={[styles.modalContent, stylesHook.modalContent]}>
<BlueTextCentered>{loc.multisig.type_your_mnemonics}</BlueTextCentered>
<BlueSpacing20 />
@ -562,12 +558,9 @@ const ViewEditMultisigCosigners: React.FC = () => {
};
const renderShareModal = () => {
// @ts-ignore weird, property exists on typedefinition. might be some ts bugs
const isPad: boolean = Platform.isPad;
return (
<BottomModal isVisible={isShareModalVisible} onClose={hideShareModal} doneButton coverScreen={false}>
<KeyboardAvoidingView enabled={!isPad} behavior={Platform.OS === 'ios' ? 'position' : undefined}>
<KeyboardAvoidingView enabled={!isTablet} behavior={Platform.OS === 'ios' ? 'position' : undefined}>
<View style={[styles.modalContent, stylesHook.modalContent, styles.alignItemsCenter]}>
<Text style={[styles.headerText, stylesHook.textDestination]}>
{loc.multisig.this_is_cosigners_xpub} {Platform.OS === 'ios' ? loc.multisig.this_is_cosigners_xpub_airdrop : ''}
@ -628,13 +621,11 @@ const ViewEditMultisigCosigners: React.FC = () => {
};
const footer = <Button disabled={vaultKeyData.isLoading || isSaveButtonDisabled} title={loc._.save} onPress={onSave} />;
// @ts-ignore weird, property exists on typedefinition. might be some ts bugs
const isPad: boolean = Platform.isPad;
return (
<View style={[styles.root, stylesHook.root]} ref={discardChangesRef}>
<KeyboardAvoidingView
enabled={!isPad}
enabled={!isTablet}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={62}
style={[styles.mainBlock, styles.root]}

View file

@ -151,69 +151,75 @@ const WalletsList: React.FC = () => {
walletsCount.current = wallets.length;
}, [wallets]);
const verifyBalance = () => {
const verifyBalance = useCallback(() => {
if (getBalance() !== 0) {
A(A.ENUM.GOT_NONZERO_BALANCE);
} else {
A(A.ENUM.GOT_ZERO_BALANCE);
}
};
}, [getBalance]);
/**
* Forcefully fetches TXs and balance for ALL wallets.
* Triggered manually by user on pull-to-refresh.
*/
const refreshTransactions = async (showLoadingIndicator = true, showUpdateStatusIndicator = false) => {
if (isElectrumDisabled) {
dispatch({ type: ActionTypes.SET_LOADING, payload: false });
return;
}
dispatch({ type: ActionTypes.SET_LOADING, payload: showLoadingIndicator });
refreshAllWalletTransactions(undefined, showUpdateStatusIndicator).finally(() => {
dispatch({ type: ActionTypes.SET_LOADING, payload: false });
});
};
const refreshTransactions = useCallback(
async (showLoadingIndicator = true, showUpdateStatusIndicator = false) => {
if (isElectrumDisabled) {
dispatch({ type: ActionTypes.SET_LOADING, payload: false });
return;
}
dispatch({ type: ActionTypes.SET_LOADING, payload: showLoadingIndicator });
refreshAllWalletTransactions(undefined, showUpdateStatusIndicator).finally(() => {
dispatch({ type: ActionTypes.SET_LOADING, payload: false });
});
},
[isElectrumDisabled, refreshAllWalletTransactions],
);
useEffect(() => {
refreshTransactions(false, true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // call refreshTransactions() only once, when screen mounts
}, []);
const handleClick = (item?: TWallet) => {
if (item?.getID) {
const walletID = item.getID();
navigate('WalletTransactions', {
walletID,
walletType: item.type,
});
} else {
navigate('AddWalletRoot');
}
};
const setIsLoading = (value: boolean) => {
dispatch({ type: ActionTypes.SET_LOADING, payload: value });
};
const onSnapToItem = (e: { nativeEvent: { contentOffset: any } }) => {
if (!isFocused) return;
const contentOffset = e.nativeEvent.contentOffset;
const index = Math.ceil(contentOffset.x / width);
if (currentWalletIndex.current !== index) {
console.log('onSnapToItem', wallets.length === index ? 'NewWallet/Importing card' : index);
if (wallets[index] && (wallets[index].timeToRefreshBalance() || wallets[index].timeToRefreshTransaction())) {
console.log(wallets[index].getLabel(), 'thinks its time to refresh either balance or transactions. refetching both');
refreshAllWalletTransactions(index, false).finally(() => setIsLoading(false));
const handleClick = useCallback(
(item?: TWallet) => {
if (item?.getID) {
const walletID = item.getID();
navigate('WalletTransactions', {
walletID,
walletType: item.type,
});
} else {
navigate('AddWalletRoot');
}
currentWalletIndex.current = index;
} else {
console.log('onSnapToItem did not change. Most likely momentum stopped at the same index it started.');
}
};
},
[navigate],
);
const renderListHeaderComponent = () => {
const setIsLoading = useCallback((value: boolean) => {
dispatch({ type: ActionTypes.SET_LOADING, payload: value });
}, []);
const onSnapToItem = useCallback(
(e: { nativeEvent: { contentOffset: any } }) => {
if (!isFocused) return;
const contentOffset = e.nativeEvent.contentOffset;
const index = Math.ceil(contentOffset.x / width);
if (currentWalletIndex.current !== index) {
console.debug('onSnapToItem', wallets.length === index ? 'NewWallet/Importing card' : index);
if (wallets[index] && (wallets[index].timeToRefreshBalance() || wallets[index].timeToRefreshTransaction())) {
refreshAllWalletTransactions(index, false).finally(() => setIsLoading(false));
}
currentWalletIndex.current = index;
}
},
[isFocused, refreshAllWalletTransactions, setIsLoading, wallets, width],
);
const renderListHeaderComponent = useCallback(() => {
return (
<View style={[styles.listHeaderBack, stylesHook.listHeaderBack]}>
<Text textBreakStrategy="simple" style={[styles.listHeaderText, stylesHook.listHeaderText]}>
@ -221,28 +227,29 @@ const WalletsList: React.FC = () => {
</Text>
</View>
);
};
}, [stylesHook.listHeaderBack, stylesHook.listHeaderText]);
const handleLongPress = () => {
const handleLongPress = useCallback(() => {
if (wallets.length > 1) {
navigate('ReorderWallets');
} else {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
}
};
}, [navigate, wallets.length]);
const renderTransactionListsRow = (item: ExtendedTransaction) => {
return (
const renderTransactionListsRow = useCallback(
(item: ExtendedTransaction) => (
<View style={styles.transaction}>
<TransactionListItem item={item} itemPriceUnit={item.walletPreferredBalanceUnit} walletID={item.walletID} />
</View>
);
};
),
[],
);
const renderWalletsCarousel = () => {
const renderWalletsCarousel = useCallback(() => {
return (
<WalletsCarousel
// @ts-ignore: Convert to TS later
// @ts-ignore: Fix later
data={wallets.concat(false)}
extraData={[wallets]}
onPress={handleClick}
@ -254,49 +261,58 @@ const WalletsList: React.FC = () => {
scrollEnabled={isFocused}
/>
);
};
}, [handleClick, handleLongPress, isFocused, onSnapToItem, wallets]);
const renderSectionItem = (item: { section: any; item: ExtendedTransaction }) => {
switch (item.section.key) {
case WalletsListSections.CAROUSEL:
return isLargeScreen ? null : renderWalletsCarousel();
case WalletsListSections.TRANSACTIONS:
return renderTransactionListsRow(item.item);
default:
return null;
}
};
const renderSectionHeader = (section: { section: { key: any } }) => {
switch (section.section.key) {
case WalletsListSections.CAROUSEL:
return isLargeScreen ? null : <Header leftText={loc.wallets.list_title} onNewWalletPress={() => navigate('AddWalletRoot')} />;
case WalletsListSections.TRANSACTIONS:
return renderListHeaderComponent();
default:
return null;
}
};
const renderSectionFooter = (section: { section: { key: any } }) => {
switch (section.section.key) {
case WalletsListSections.TRANSACTIONS:
if (dataSource.length === 0 && !isLoading) {
return (
<View style={styles.footerRoot} testID="NoTransactionsMessage">
<Text style={styles.footerEmpty}>{loc.wallets.list_empty_txs1}</Text>
<Text style={styles.footerStart}>{loc.wallets.list_empty_txs2}</Text>
</View>
);
} else {
const renderSectionItem = useCallback(
(item: { section: any; item: ExtendedTransaction }) => {
switch (item.section.key) {
case WalletsListSections.CAROUSEL:
return isLargeScreen ? null : renderWalletsCarousel();
case WalletsListSections.TRANSACTIONS:
return renderTransactionListsRow(item.item);
default:
return null;
}
default:
return null;
}
};
}
},
[isLargeScreen, renderTransactionListsRow, renderWalletsCarousel],
);
const renderScanButton = () => {
const renderSectionHeader = useCallback(
(section: { section: { key: any } }) => {
switch (section.section.key) {
case WalletsListSections.CAROUSEL:
return isLargeScreen ? null : <Header leftText={loc.wallets.list_title} onNewWalletPress={() => navigate('AddWalletRoot')} />;
case WalletsListSections.TRANSACTIONS:
return renderListHeaderComponent();
default:
return null;
}
},
[isLargeScreen, navigate, renderListHeaderComponent],
);
const renderSectionFooter = useCallback(
(section: { section: { key: any } }) => {
switch (section.section.key) {
case WalletsListSections.TRANSACTIONS:
if (dataSource.length === 0 && !isLoading) {
return (
<View style={styles.footerRoot} testID="NoTransactionsMessage">
<Text style={styles.footerEmpty}>{loc.wallets.list_empty_txs1}</Text>
<Text style={styles.footerStart}>{loc.wallets.list_empty_txs2}</Text>
</View>
);
} else {
return null;
}
default:
return null;
}
},
[dataSource.length, isLoading],
);
const renderScanButton = useCallback(() => {
if (wallets.length > 0) {
return (
<FContainer ref={walletActionButtonsRef.current}>
@ -311,30 +327,35 @@ const WalletsList: React.FC = () => {
} else {
return null;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [scanImage, wallets.length]);
const sectionListKeyExtractor = (item: any, index: any) => {
return `${item}${index}}`;
};
const onScanButtonPressed = () => {
scanQrHelper(navigate, routeName).then(onBarScanned);
};
const onScanButtonPressed = useCallback(() => {
scanQrHelper(routeName).then(onBarScanned);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onBarScanned = (value: any) => {
if (!value) return;
DeeplinkSchemaMatch.navigationRouteFor({ url: value }, completionValue => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
// @ts-ignore: Fix later
navigate(...completionValue);
});
};
const onBarScanned = useCallback(
(value: any) => {
if (!value) return;
DeeplinkSchemaMatch.navigationRouteFor({ url: value }, completionValue => {
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
// @ts-ignore: for now
navigate(...completionValue);
});
},
[navigate],
);
const copyFromClipboard = async () => {
const copyFromClipboard = useCallback(async () => {
onBarScanned(await BlueClipboard().getClipboardContent());
};
}, [onBarScanned]);
const sendButtonLongPress = async () => {
const sendButtonLongPress = useCallback(async () => {
const isClipboardEmpty = (await BlueClipboard().getClipboardContent()).trim().length === 0;
const options = [loc._.cancel, loc.wallets.list_long_choose, loc.wallets.list_long_scan];
@ -358,13 +379,12 @@ const WalletsList: React.FC = () => {
fs.showImagePickerAndReadImage()
.then(onBarScanned)
.catch(error => {
console.log(error);
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ title: loc.errors.error, message: error.message });
});
break;
case 2:
scanQrHelper(navigate, routeName, true).then(data => onBarScanned(data));
scanQrHelper(routeName, true).then(data => onBarScanned(data));
break;
case 3:
if (!isClipboardEmpty) {
@ -373,12 +393,13 @@ const WalletsList: React.FC = () => {
break;
}
});
};
}, [copyFromClipboard, onBarScanned, routeName]);
const onRefresh = () => {
const onRefresh = useCallback(() => {
refreshTransactions(true, false);
};
// Optimized for Mac option doesn't like RN Refresh component. Menu Elements now handles it for macOS
// Optimized for Mac option doesn't like RN Refresh component. Menu Elements now handles it for macOS
}, [refreshTransactions]);
const refreshProps = isDesktop || isElectrumDisabled ? {} : { refreshing: isLoading, onRefresh };
const sections: SectionData[] = [
@ -401,6 +422,9 @@ const WalletsList: React.FC = () => {
contentInset={styles.scrollContent}
renderSectionFooter={renderSectionFooter}
sections={sections}
windowSize={21}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
/>
{renderScanButton()}
</View>

View file

@ -461,7 +461,7 @@ const WalletsAddMultisigStep2 = () => {
const scanOrOpenFile = () => {
setIsProvideMnemonicsModalVisible(false);
InteractionManager.runAfterInteractions(async () => {
const scanned = await scanQrHelper(navigation.navigate, name, true);
const scanned = await scanQrHelper(name, true);
onBarScanned({ data: scanned });
});
};

View file

@ -409,7 +409,7 @@ const WalletTransactions = ({ navigation }) => {
choosePhoto();
break;
case 2:
scanQrHelper(navigate, name, true).then(data => onBarCodeRead(data));
scanQrHelper(name, true).then(data => onBarCodeRead(data));
break;
case 3:
if (!isClipboardEmpty) {