mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-23 23:27:26 +01:00
Merge branch 'master' into rn738
This commit is contained in:
commit
14c80f5cf6
31 changed files with 739 additions and 566 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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 = '',
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -1 +1 @@
|
|||
BlueWallet - Bitcoin Wallet
|
||||
BlueWallet - Bitcoin Wallet
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
272
screen/settings/EncryptStorage.tsx
Normal file
272
screen/settings/EncryptStorage.tsx
Normal 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;
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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 (
|
||||
|
|
|
@ -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,
|
|
@ -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]}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 });
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue