feat: redesigned wallet details (#8301)
* feat: redesign transaction detail screen with unified layout and Lottie pending animation * ADD: decode OP_RETURN payload as UTF-8 text in transaction detail Co-authored-by: Cursor <cursoragent@cursor.com> * REF: transaction detail redesign (themes, pending icon, loc) Co-authored-by: Cursor <cursoragent@cursor.com> * REF: remove deprecated TransactionDetails, TransactionStatus and getTransactionStatusOptions Co-authored-by: Cursor <cursoragent@cursor.com> * FIX: resolve lint errors (unused vars, styles, loc keys, no-bitwise, inline styles) Co-authored-by: Cursor <cursoragent@cursor.com> * FIX: remove redundant !tx check in transaction detail guard Co-authored-by: Cursor <cursoragent@cursor.com> * FIX: show transaction not available when tx not found after load Co-authored-by: Cursor <cursoragent@cursor.com> * FIX: remove unused transaction prop type from TransactionDetail Co-authored-by: Cursor <cursoragent@cursor.com> * TST: update UTXO note E2E to use new transaction detail note prompt UI Co-authored-by: Cursor <cursoragent@cursor.com> * simplify changes on the PR for review * remove unused loc * remove unchanged colors * better offline support for tx details * remove unused key loc * fix code review issues * fix balance * fix tests * REF: address PR #8289 review feedback * redesigned wallets details * fix lint * fix lint * fix bip84 test * fix test * fix tests * fix tests * fix: truncation and sendTo logic display * fix: new arch fixes * fix: lint * fix: crash on status update * fix: lint and tests * fix: tests * fix: tests * fix: tests * fix: tests * fix: tests * Potential fix for pull request finding 'Identical operands' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * fix: tests * fix: tests * fix: tests * fix: tests * fix: tests * fix style * fix merge master * Merge branch 'wallet-details' of https://github.com/BlueWallet/BlueWallet into wallet-details * fix loc * fix loc * fix style * improve coin control from wallet details * fix: e2e * fix: WalletDetails * fix: flat * fix: e2e * fix: e2e * Potential fix for pull request finding 'Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * fix: remove notifications dialogs * fix: second button title --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Ivan Vershigora <ivan.vershigora@gmail.com> Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> Co-authored-by: Overtorment <overtorment@gmail.com>
This commit is contained in:
parent
c1c13e9e58
commit
9e907566f0
@ -197,12 +197,13 @@ export class WatchOnlyWallet extends LegacyWallet {
|
||||
|
||||
async fetchUtxo() {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance.fetchUtxo();
|
||||
throw new Error('Not initialized');
|
||||
// Single-address watch-only uses LegacyWallet UTXO + derivation from txs (no HD instance).
|
||||
return super.fetchUtxo();
|
||||
}
|
||||
|
||||
getUtxo(...args: Parameters<THDWalletForWatchOnly['getUtxo']>) {
|
||||
if (this._hdWalletInstance) return this._hdWalletInstance.getUtxo(...args);
|
||||
throw new Error('Not initialized');
|
||||
return super.getUtxo(...args);
|
||||
}
|
||||
|
||||
combinePsbt(...args: Parameters<THDWalletForWatchOnly['combinePsbt']>) {
|
||||
|
||||
@ -11,10 +11,12 @@ interface ListItemProps {
|
||||
noFeedback?: boolean;
|
||||
bottomDivider?: boolean;
|
||||
testID?: string;
|
||||
switchTestID?: string;
|
||||
onPress?: () => void;
|
||||
disabled?: boolean;
|
||||
switch?: SwitchProps;
|
||||
title: string;
|
||||
titleStyle?: StyleProp<TextStyle>;
|
||||
subtitle?: string | React.ReactNode;
|
||||
subtitleNumberOfLines?: number;
|
||||
rightTitle?: string;
|
||||
@ -33,10 +35,12 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||
noFeedback = false,
|
||||
bottomDivider = true,
|
||||
testID,
|
||||
switchTestID,
|
||||
onPress,
|
||||
disabled,
|
||||
switch: switchProps,
|
||||
title,
|
||||
titleStyle,
|
||||
subtitle,
|
||||
subtitleNumberOfLines,
|
||||
rightTitle,
|
||||
@ -83,6 +87,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||
const memoizedSwitchProps = useMemo(() => {
|
||||
return switchProps ? { ...switchProps } : undefined;
|
||||
}, [switchProps]);
|
||||
const resolvedSwitchTestID = switchTestID ?? memoizedSwitchProps?.testID;
|
||||
const enableFeedback = !noFeedback && !!onPress && !disabled;
|
||||
|
||||
const renderContent = () => (
|
||||
@ -94,7 +99,7 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.content}>
|
||||
<Text style={stylesHook.title} numberOfLines={0} accessibilityRole="text">
|
||||
<Text style={[stylesHook.title, titleStyle]} numberOfLines={0} accessibilityRole="text">
|
||||
{title}
|
||||
</Text>
|
||||
{subtitle ? (
|
||||
@ -124,7 +129,14 @@ const ListItem: React.FC<ListItemProps> = React.memo(
|
||||
<Icon name={isRtl ? 'angle-left' : 'angle-right'} type="font-awesome" color={colors.alternativeTextColor} size={18} />
|
||||
) : null}
|
||||
{switchProps ? (
|
||||
<Switch {...memoizedSwitchProps} accessibilityLabel={title} style={styles.margin16} accessible accessibilityRole="switch" />
|
||||
<Switch
|
||||
{...memoizedSwitchProps}
|
||||
testID={resolvedSwitchTestID}
|
||||
accessibilityLabel={title}
|
||||
style={styles.margin16}
|
||||
accessible
|
||||
accessibilityRole="switch"
|
||||
/>
|
||||
) : null}
|
||||
{checkmark ? (
|
||||
<View style={styles.checkmarkContainer}>
|
||||
|
||||
@ -11,6 +11,7 @@ type SecondButtonProps = {
|
||||
disabled?: boolean;
|
||||
icon?: IconButtonProps;
|
||||
title: string;
|
||||
textColor?: string;
|
||||
onPress?: () => void;
|
||||
loading?: boolean;
|
||||
testID?: string;
|
||||
@ -19,7 +20,7 @@ type SecondButtonProps = {
|
||||
export const SecondButton = forwardRef<React.ElementRef<typeof TouchableOpacity>, SecondButtonProps>((props, ref) => {
|
||||
const { colors } = useTheme();
|
||||
let backgroundColor = props.backgroundColor ? props.backgroundColor : colors.buttonGrayBackgroundColor;
|
||||
let fontColor = colors.secondButtonTextColor;
|
||||
let fontColor = props.textColor ?? colors.secondButtonTextColor;
|
||||
if (props.disabled === true) {
|
||||
backgroundColor = colors.buttonDisabledBackgroundColor;
|
||||
fontColor = colors.buttonDisabledTextColor;
|
||||
|
||||
@ -66,10 +66,13 @@ const getHandleCloseAction = (
|
||||
const navigationStyle = (
|
||||
{
|
||||
closeButtonPosition,
|
||||
closeButtonIfFirstInStack,
|
||||
onCloseButtonPressed,
|
||||
...opts
|
||||
}: NativeStackNavigationOptions & {
|
||||
closeButtonPosition?: CloseButtonPosition;
|
||||
/** When set, show this close control only if this screen is the first route in the stack (e.g. Coin Control opened from wallet details). */
|
||||
closeButtonIfFirstInStack?: CloseButtonPosition;
|
||||
onCloseButtonPressed?: (deps: { navigation: any; route: any }) => void;
|
||||
},
|
||||
formatter?: OptionsFormatter,
|
||||
@ -80,7 +83,10 @@ const navigationStyle = (
|
||||
const isModal = route.params?.presentation === 'modal' || route.params?.presentation === 'transparentModal';
|
||||
const isFormSheet = route.params?.presentation === 'formSheet';
|
||||
|
||||
const closeButton = getCloseButtonPosition(closeButtonPosition, isFirstRouteInStack, isModal);
|
||||
const closeButton =
|
||||
closeButtonIfFirstInStack && isFirstRouteInStack
|
||||
? closeButtonIfFirstInStack
|
||||
: getCloseButtonPosition(closeButtonPosition, isFirstRouteInStack, isModal);
|
||||
const handleClose = getHandleCloseAction(onCloseButtonPressed, navigation, route);
|
||||
|
||||
let headerRight;
|
||||
|
||||
@ -376,7 +376,6 @@
|
||||
"rbf_title": "Speed Up (RBF)",
|
||||
"status_bump": "Speed Up",
|
||||
"status_cancel": "Cancel",
|
||||
"transactions_count": "Transactions Count",
|
||||
"txid": "Transaction ID",
|
||||
"updating": "Updating...",
|
||||
"watchOnlyWarningTitle": "Security warning",
|
||||
@ -438,12 +437,15 @@
|
||||
"details_delete_wallet": "Delete Wallet",
|
||||
"details_derivation_path": "derivation path",
|
||||
"details_display": "Display in Home Screen",
|
||||
"details_edit": "edit",
|
||||
"details_export_backup": "Export/Backup",
|
||||
"details_export_history": "Export History to CSV",
|
||||
"details_master_fingerprint": "Master Fingerprint",
|
||||
"details_multisig_type": "multisig",
|
||||
"details_options": "Options",
|
||||
"details_show_xpub": "Show Wallet XPUB",
|
||||
"details_show_addresses": "Show addresses",
|
||||
"details_stats_coins": "Coins",
|
||||
"details_title": "Wallet",
|
||||
"wallets": "Wallets",
|
||||
"swipe_balance_hide": "Hide",
|
||||
|
||||
@ -272,7 +272,11 @@ const DetailViewStackScreensStack = () => {
|
||||
name="WalletDetails"
|
||||
component={WalletDetails}
|
||||
options={navigationStyle({
|
||||
headerTitle: loc.wallets.details_title,
|
||||
headerTitle: '',
|
||||
statusBarStyle: 'auto',
|
||||
headerStyle: {
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
})(theme)}
|
||||
/>
|
||||
<DetailViewStack.Screen
|
||||
|
||||
@ -115,7 +115,14 @@ const SendDetailsStack = () => {
|
||||
closeButtonPosition: CloseButtonPosition.Right,
|
||||
})(theme)}
|
||||
/>
|
||||
<Stack.Screen name="CoinControl" component={CoinControlComponent} options={navigationStyle({ title: loc.cc.header })(theme)} />
|
||||
<Stack.Screen
|
||||
name="CoinControl"
|
||||
component={CoinControlComponent}
|
||||
options={navigationStyle({
|
||||
title: loc.cc.header,
|
||||
closeButtonIfFirstInStack: CloseButtonPosition.Left,
|
||||
})(theme)}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="PaymentCodeList"
|
||||
component={PaymentCodesListComponent}
|
||||
|
||||
39
navigation/goFromCoinControlToSendDetails.ts
Normal file
39
navigation/goFromCoinControlToSendDetails.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { CommonActions, StackActions } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import { Utxo } from '../class/wallets/types';
|
||||
import { BitcoinUnit } from '../models/bitcoinUnits';
|
||||
import { SendDetailsStackParamList } from './SendDetailsStackParamList';
|
||||
|
||||
/**
|
||||
* After choosing UTXO(s) in Coin Control, open Send with those coins.
|
||||
* Uses popTo when SendDetails is already in the stack (normal send → coin control).
|
||||
* Resets the send stack when Coin Control was opened as the first screen (e.g. from wallet details).
|
||||
*/
|
||||
export function goFromCoinControlToSendDetails(
|
||||
navigation: NativeStackNavigationProp<SendDetailsStackParamList>,
|
||||
walletID: string,
|
||||
utxos: Utxo[],
|
||||
): void {
|
||||
const state = navigation.getState();
|
||||
const hasSendDetails = state.routes.some(r => r.name === 'SendDetails');
|
||||
|
||||
const params = {
|
||||
walletID,
|
||||
utxos,
|
||||
isEditable: true as const,
|
||||
feeUnit: BitcoinUnit.BTC,
|
||||
amountUnit: BitcoinUnit.BTC,
|
||||
};
|
||||
|
||||
if (hasSendDetails) {
|
||||
navigation.dispatch(StackActions.popTo('SendDetails', params, { merge: true }));
|
||||
} else {
|
||||
navigation.dispatch(
|
||||
CommonActions.reset({
|
||||
index: 0,
|
||||
routes: [{ name: 'SendDetails', params }],
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { RouteProp, StackActions, useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import { RouteProp, useFocusEffect, useRoute } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import Avatar from '../../components/Avatar';
|
||||
import Badge from '../../components/Badge';
|
||||
@ -17,6 +17,7 @@ import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import loc, { formatBalance } from '../../loc';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { goFromCoinControlToSendDetails } from '../../navigation/goFromCoinControlToSendDetails';
|
||||
import { SendDetailsStackParamList } from '../../navigation/SendDetailsStackParamList';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
|
||||
@ -260,9 +261,8 @@ const CoinControl: React.FC = () => {
|
||||
|
||||
const handleChoose = (item: Utxo) => navigation.navigate('CoinControlOutput', { walletID, utxo: item });
|
||||
|
||||
const handleUseCoin = async (u: Utxo[]) => {
|
||||
const popToAction = StackActions.popTo('SendDetails', { walletID, utxos: u }, { merge: true });
|
||||
navigation.dispatch(popToAction);
|
||||
const handleUseCoin = (u: Utxo[]) => {
|
||||
goFromCoinControlToSendDetails(navigation, walletID, u);
|
||||
};
|
||||
|
||||
const handleMassFreeze = () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { RouteProp, StackActions, useRoute } from '@react-navigation/native';
|
||||
import { RouteProp, useRoute } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { StyleSheet, Text, TextInput, View } from 'react-native';
|
||||
import debounce from '../../blue_modules/debounce';
|
||||
@ -10,6 +10,7 @@ import Button from '../../components/Button';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import loc, { formatBalance } from '../../loc';
|
||||
import { BitcoinUnit } from '../../models/bitcoinUnits';
|
||||
import { goFromCoinControlToSendDetails } from '../../navigation/goFromCoinControlToSendDetails';
|
||||
import { SendDetailsStackParamList } from '../../navigation/SendDetailsStackParamList';
|
||||
import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
@ -81,8 +82,7 @@ const CoinControlOutputSheet: React.FC = () => {
|
||||
debouncedSaveMemo.current.cancel();
|
||||
wallet.setUTXOMetadata(utxo.txid, utxo.vout, { memo });
|
||||
await saveToDisk();
|
||||
const popToAction = StackActions.popTo('SendDetails', { walletID, utxos: [utxo] }, { merge: true });
|
||||
navigation.dispatch(popToAction);
|
||||
goFromCoinControlToSendDetails(navigation, walletID, [utxo]);
|
||||
}, [memo, navigation, saveToDisk, utxo, wallet, walletID]);
|
||||
|
||||
const applyChangesAndClose = useCallback(async () => {
|
||||
|
||||
@ -93,6 +93,8 @@ const SendDetails = () => {
|
||||
const routeParams = route.params;
|
||||
const scrollView = useRef<FlatList<any>>(null);
|
||||
const scrollIndex = useRef(0);
|
||||
/** Used so we only clear coin-selection (utxos) when the user switches wallet, not on first mount (e.g. Send opened from wallet details with pre-selected UTXOs). */
|
||||
const prevWalletIdForCoinResetRef = useRef<string | null>(null);
|
||||
const { colors } = useTheme();
|
||||
|
||||
// state
|
||||
@ -274,17 +276,20 @@ const SendDetails = () => {
|
||||
useEffect(() => {
|
||||
if (!wallet) return;
|
||||
|
||||
// reset other values
|
||||
setChangeAddress(null);
|
||||
const prevId = prevWalletIdForCoinResetRef.current;
|
||||
const currentId = wallet.getID();
|
||||
const walletActuallyChanged = prevId !== null && prevId !== currentId;
|
||||
|
||||
setParams({
|
||||
utxos: null,
|
||||
...(walletActuallyChanged ? { utxos: null } : {}),
|
||||
isTransactionReplaceable: wallet.type === HDSegwitBech32Wallet.type && !routeParams.isTransactionReplaceable ? true : undefined,
|
||||
});
|
||||
// update wallet UTXO
|
||||
prevWalletIdForCoinResetRef.current = currentId;
|
||||
|
||||
wallet
|
||||
.fetchUtxo()
|
||||
.then(() => {
|
||||
// we need to re-calculate fees
|
||||
setDumb(v => !v);
|
||||
})
|
||||
.catch(e => console.log('fetchUtxo error', e));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ActivityIndicator, StyleSheet, Switch, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { writeFileAndExport } from '../../blue_modules/fs';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import { uint8ArrayToHex } from '../../blue_modules/uint8array-extras';
|
||||
@ -16,7 +16,7 @@ import { WatchOnlyWallet } from '../../class/wallets/watch-only-wallet';
|
||||
import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electrum-wallet';
|
||||
import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
import CopyTextToClipboard from '../../components/CopyTextToClipboard';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import { SecondButton } from '../../components/SecondButton';
|
||||
import { useTheme } from '../../components/themes';
|
||||
@ -29,16 +29,27 @@ import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { useFocusEffect, useRoute, RouteProp, usePreventRemove, useLocale } from '@react-navigation/native';
|
||||
import { LightningTransaction, Transaction, TWallet } from '../../class/wallets/types';
|
||||
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
|
||||
import HeaderMenuButton from '../../components/HeaderMenuButton';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import { Action } from '../../components/types';
|
||||
import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
|
||||
import SafeAreaScrollView from '../../components/SafeAreaScrollView';
|
||||
import { BlueSpacing10, BlueSpacing20 } from '../../components/BlueSpacing';
|
||||
import { BlueSpacing20 } from '../../components/BlueSpacing';
|
||||
import { BlueLoading } from '../../components/BlueLoading';
|
||||
import Icon from '../../components/Icon';
|
||||
|
||||
type RouteProps = RouteProp<DetailViewStackParamList, 'WalletDetails'>;
|
||||
|
||||
function getCoinControlStats(w: TWallet): { hasCoinControl: boolean; utxoCount: number | null } {
|
||||
if (typeof w.getUtxo !== 'function') return { hasCoinControl: false, utxoCount: null };
|
||||
try {
|
||||
return { hasCoinControl: true, utxoCount: w.getUtxo().length };
|
||||
} catch {
|
||||
return { hasCoinControl: false, utxoCount: null };
|
||||
}
|
||||
}
|
||||
|
||||
const WalletDetails: React.FC = () => {
|
||||
const { saveToDisk, wallets, txMetadata, handleWalletDeletion } = useStorage();
|
||||
const { saveToDisk, wallets, txMetadata, handleWalletDeletion, sleep } = useStorage();
|
||||
const { isBiometricUseCapableAndEnabled } = useBiometrics();
|
||||
const { walletID } = useRoute<RouteProps>().params;
|
||||
const { direction } = useLocale();
|
||||
@ -60,11 +71,42 @@ const WalletDetails: React.FC = () => {
|
||||
);
|
||||
const { setOptions, navigate, navigateToWalletsList } = useExtendedNavigation();
|
||||
const { colors } = useTheme();
|
||||
const [walletName, setWalletName] = useState<string>(wallet.getLabel());
|
||||
|
||||
const [masterFingerprint, setMasterFingerprint] = useState<string | undefined>();
|
||||
const [arkAddress, setArkAddress] = useState<string>('');
|
||||
const walletTransactionsLength = useMemo<number>(() => wallet.getTransactions().length, [wallet]);
|
||||
const [coinControlStats, setCoinControlStats] = useState(() => getCoinControlStats(wallet));
|
||||
|
||||
useEffect(() => {
|
||||
const w = walletRef.current;
|
||||
if (w) setCoinControlStats(getCoinControlStats(w));
|
||||
}, [walletID]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
let cancelled = false;
|
||||
const w = walletRef.current;
|
||||
if (!w || typeof w.getUtxo !== 'function') return;
|
||||
|
||||
const refresh = async () => {
|
||||
if (typeof w.fetchUtxo === 'function') {
|
||||
try {
|
||||
await Promise.race([w.fetchUtxo(), sleep(12000)]);
|
||||
} catch {
|
||||
// Same pattern as CoinControl: timeout or network errors; still re-read getUtxo() below.
|
||||
}
|
||||
}
|
||||
if (!cancelled) setCoinControlStats(getCoinControlStats(w));
|
||||
};
|
||||
|
||||
refresh().catch(() => {});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [sleep]),
|
||||
);
|
||||
|
||||
const { hasCoinControl, utxoCount } = coinControlStats;
|
||||
const derivationPath = useMemo<string | null>(() => {
|
||||
try {
|
||||
// @ts-expect-error: Need to fix later
|
||||
@ -79,6 +121,7 @@ const WalletDetails: React.FC = () => {
|
||||
}
|
||||
}, [wallet]);
|
||||
const [isMasterFingerPrintVisible, setIsMasterFingerPrintVisible] = useState<boolean>(false);
|
||||
const [isAdvancedExpanded, setIsAdvancedExpanded] = useState<boolean>(false);
|
||||
|
||||
// Fetch ark address when wallet is a LightningArkWallet
|
||||
useEffect(() => {
|
||||
@ -86,7 +129,6 @@ const WalletDetails: React.FC = () => {
|
||||
if (wallet.type === LightningArkWallet.type && wallet.getArkAddress) {
|
||||
try {
|
||||
const address = await wallet.getArkAddress();
|
||||
console.log('ark address:', address);
|
||||
setArkAddress(address);
|
||||
} catch (error: any) {
|
||||
setArkAddress(error.message);
|
||||
@ -215,19 +257,17 @@ const WalletDetails: React.FC = () => {
|
||||
|
||||
const toolTipOnPressMenuItem = useCallback(
|
||||
async (id: string) => {
|
||||
if (id === CommonToolTipActions.Delete.id) {
|
||||
handleDeleteButtonTapped();
|
||||
} else if (id === CommonToolTipActions.Share.id) {
|
||||
if (id === CommonToolTipActions.Share.id) {
|
||||
await writeFileAndExport(fileName, exportHistoryContent(), true);
|
||||
} else if (id === CommonToolTipActions.SaveFile.id) {
|
||||
await writeFileAndExport(fileName, exportHistoryContent(), false);
|
||||
}
|
||||
},
|
||||
[exportHistoryContent, fileName, handleDeleteButtonTapped],
|
||||
[exportHistoryContent, fileName],
|
||||
);
|
||||
|
||||
const toolTipActions = useMemo(() => {
|
||||
const actions: Action[] = [
|
||||
const transactionsBoxMenuActions = useMemo(
|
||||
(): Action[] => [
|
||||
{
|
||||
id: loc.wallets.details_export_history,
|
||||
text: loc.wallets.details_export_history,
|
||||
@ -235,22 +275,15 @@ const WalletDetails: React.FC = () => {
|
||||
hidden: walletTransactionsLength === 0,
|
||||
subactions: [CommonToolTipActions.Share, CommonToolTipActions.SaveFile],
|
||||
},
|
||||
CommonToolTipActions.Delete,
|
||||
];
|
||||
|
||||
return actions;
|
||||
}, [walletTransactionsLength]);
|
||||
|
||||
const HeaderRight = useMemo(
|
||||
() => <HeaderMenuButton disabled={isLoading} onPressMenuItem={toolTipOnPressMenuItem} actions={toolTipActions} />,
|
||||
[toolTipOnPressMenuItem, toolTipActions, isLoading],
|
||||
],
|
||||
[walletTransactionsLength],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setOptions({
|
||||
headerRight: () => HeaderRight,
|
||||
headerRight: undefined,
|
||||
});
|
||||
}, [HeaderRight, setOptions]);
|
||||
}, [setOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsContactsVisible(wallet.allowBIP47 && wallet.allowBIP47() && isBIP47Enabled);
|
||||
@ -277,20 +310,72 @@ const WalletDetails: React.FC = () => {
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
textLabel1: {
|
||||
color: colors.feeText,
|
||||
color: colors.alternativeTextColor,
|
||||
writingDirection: direction,
|
||||
},
|
||||
textLabel2: {
|
||||
color: colors.feeText,
|
||||
color: colors.alternativeTextColor,
|
||||
writingDirection: direction,
|
||||
},
|
||||
textValue: {
|
||||
color: colors.outputValue,
|
||||
},
|
||||
input: {
|
||||
borderColor: colors.formBorder,
|
||||
borderBottomColor: colors.formBorder,
|
||||
backgroundColor: colors.inputBackgroundColor,
|
||||
walletNameText: {
|
||||
color: colors.outputValue,
|
||||
writingDirection: direction,
|
||||
},
|
||||
nameRow: {
|
||||
flexDirection: direction === 'rtl' ? 'row-reverse' : 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
editButton: {
|
||||
backgroundColor: colors.lightButton,
|
||||
marginLeft: direction === 'rtl' ? 0 : 12,
|
||||
marginRight: direction === 'rtl' ? 12 : 0,
|
||||
},
|
||||
editButtonText: {
|
||||
color: colors.buttonTextColor,
|
||||
},
|
||||
optionsSectionHeader: {
|
||||
borderColor: colors.cardBorderColor,
|
||||
},
|
||||
detailsCard: {
|
||||
borderColor: colors.cardBorderColor,
|
||||
},
|
||||
sectionTitle: {
|
||||
backgroundColor: colors.cardSectionHeaderBackground,
|
||||
},
|
||||
sectionTitleText: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
optionsContent: {
|
||||
backgroundColor: colors.cardSectionBackground,
|
||||
borderBottomLeftRadius: 12,
|
||||
borderBottomRightRadius: 12,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
advancedContent: {
|
||||
backgroundColor: colors.cardSectionBackground,
|
||||
borderBottomLeftRadius: 12,
|
||||
borderBottomRightRadius: 12,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
advancedListItemTitle: {
|
||||
color: colors.feeText,
|
||||
writingDirection: direction,
|
||||
},
|
||||
advancedListItemRightTitle: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
statsBox: {
|
||||
backgroundColor: colors.cardSectionHeaderBackground,
|
||||
},
|
||||
statsBoxNumber: {
|
||||
color: colors.foregroundColor,
|
||||
},
|
||||
listItemContainerBorder: {
|
||||
backgroundColor: 'transparent',
|
||||
borderBottomColor: colors.cardBorderColor,
|
||||
},
|
||||
});
|
||||
|
||||
@ -399,26 +484,20 @@ const WalletDetails: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const walletNameTextInputOnBlur = useCallback(async () => {
|
||||
const trimmedWalletName = walletName.trim();
|
||||
if (trimmedWalletName.length === 0) {
|
||||
const walletLabel = wallet.getLabel();
|
||||
setWalletName(walletLabel);
|
||||
} else if (wallet.getLabel() !== trimmedWalletName) {
|
||||
// Only save if the name has changed
|
||||
wallet.setLabel(trimmedWalletName);
|
||||
try {
|
||||
console.warn('saving wallet name:', trimmedWalletName);
|
||||
await saveToDisk();
|
||||
} catch (error) {
|
||||
console.error((error as Error).message);
|
||||
}
|
||||
const handleEditWalletName = useCallback(async () => {
|
||||
try {
|
||||
const newName = await prompt(loc.wallets.add_wallet_name, '', true, 'plain-text', false, undefined, wallet.getLabel());
|
||||
const trimmed = newName.trim();
|
||||
if (trimmed.length === 0) return;
|
||||
if (wallet.getLabel() === trimmed) return;
|
||||
wallet.setLabel(trimmed);
|
||||
await saveToDisk();
|
||||
} catch (_) {
|
||||
// User cancelled
|
||||
}
|
||||
}, [wallet, walletName, saveToDisk]);
|
||||
}, [wallet, saveToDisk]);
|
||||
|
||||
usePreventRemove(false, () => {
|
||||
walletNameTextInputOnBlur();
|
||||
});
|
||||
usePreventRemove(false, () => {});
|
||||
|
||||
const onViewMasterFingerPrintPress = () => {
|
||||
setIsMasterFingerPrintVisible(true);
|
||||
@ -432,79 +511,27 @@ const WalletDetails: React.FC = () => {
|
||||
) : (
|
||||
<>
|
||||
<BlueCard style={styles.address}>
|
||||
{(() => {
|
||||
if (
|
||||
[LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(wallet.type) ||
|
||||
(wallet.type === WatchOnlyWallet.type && !wallet.isHd())
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_address.toLowerCase()}</Text>
|
||||
<Text style={[styles.textValue, stylesHook.textValue]} selectable>
|
||||
{(() => {
|
||||
// gracefully handling faulty wallets, so at least user has an option to delete the wallet
|
||||
try {
|
||||
return wallet.getAddress ? wallet.getAddress() : '';
|
||||
} catch (error: any) {
|
||||
return error.message;
|
||||
}
|
||||
})()}
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.add_wallet_name.toLowerCase()}</Text>
|
||||
<View style={[styles.input, stylesHook.input]}>
|
||||
<TextInput
|
||||
value={walletName}
|
||||
onChangeText={(text: string) => {
|
||||
setWalletName(text);
|
||||
}}
|
||||
onChange={event => {
|
||||
const text = event.nativeEvent.text;
|
||||
setWalletName(text);
|
||||
}}
|
||||
onBlur={walletNameTextInputOnBlur}
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.add_wallet_name}</Text>
|
||||
<View style={[styles.nameRow, stylesHook.nameRow]}>
|
||||
<Text
|
||||
style={[styles.nameValue, stylesHook.walletNameText]}
|
||||
numberOfLines={1}
|
||||
placeholderTextColor="#81868e"
|
||||
style={[styles.inputText, { writingDirection: direction }]}
|
||||
editable={!isLoading}
|
||||
underlineColorAndroid="transparent"
|
||||
testID="WalletNameInput"
|
||||
/>
|
||||
ellipsizeMode="tail"
|
||||
testID="WalletNameDisplay"
|
||||
>
|
||||
{wallet.getLabel()}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
style={[styles.editButton, stylesHook.editButton]}
|
||||
onPress={handleEditWalletName}
|
||||
disabled={isLoading}
|
||||
accessibilityRole="button"
|
||||
testID="WalletNameEditButton"
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<BlueText style={[styles.editButtonText, stylesHook.editButtonText]}>{loc.wallets.details_edit}</BlueText>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<BlueSpacing20 />
|
||||
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_type.toLowerCase()}</Text>
|
||||
<Text style={[styles.textValue, stylesHook.textValue]} selectable>
|
||||
{wallet.typeReadable}
|
||||
</Text>
|
||||
{wallet.type === LightningArkWallet.type && (
|
||||
<>
|
||||
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>Ark {loc.wallets.details_address.toLowerCase()}</Text>
|
||||
<Text style={[styles.textValue, stylesHook.textValue]} selectable>
|
||||
{arkAddress}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
<>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_multisig_type}</Text>
|
||||
<BlueText>
|
||||
{`${wallet.getM()} / ${wallet.getN()} (${
|
||||
wallet.isNativeSegwit() ? 'native segwit' : wallet.isWrappedSegwit() ? 'wrapped segwit' : 'legacy'
|
||||
})`}
|
||||
</BlueText>
|
||||
</>
|
||||
)}
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
<>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.multisig.how_many_signatures_can_bluewallet_make}</Text>
|
||||
<BlueText>{wallet.howManySignaturesCanWeMake()}</BlueText>
|
||||
</>
|
||||
)}
|
||||
|
||||
{wallet.type === LightningCustodianWallet.type && (
|
||||
<>
|
||||
<Text style={[styles.textLabel1, stylesHook.textLabel1]}>{loc.wallets.details_connected_to.toLowerCase()}</Text>
|
||||
@ -518,74 +545,143 @@ const WalletDetails: React.FC = () => {
|
||||
<BlueText>{wallet.getIdentityPubkey()}</BlueText>
|
||||
</>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
<>
|
||||
<Text onPress={exportInternals} style={[styles.textLabel2, stylesHook.textLabel2]}>
|
||||
{loc.transactions.list_title.toLowerCase()}
|
||||
</Text>
|
||||
<View style={styles.hardware}>
|
||||
<BlueText>{loc.wallets.details_display}</BlueText>
|
||||
<Switch
|
||||
value={hideTransactionsInWalletsList}
|
||||
onValueChange={async (value: boolean) => {
|
||||
if (wallet.setHideTransactionsInWalletsList) {
|
||||
wallet.setHideTransactionsInWalletsList(!value);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
|
||||
setHideTransactionsInWalletsList(!wallet.getHideTransactionsInWalletsList());
|
||||
}
|
||||
</BlueCard>
|
||||
|
||||
{/* Address (watch-address wallets only) */}
|
||||
{([LegacyWallet.type, SegwitBech32Wallet.type, SegwitP2SHWallet.type].includes(wallet.type) ||
|
||||
(wallet.type === WatchOnlyWallet.type && !wallet.isHd())) && (
|
||||
<View style={[styles.detailsCard, stylesHook.detailsCard]}>
|
||||
<View style={stylesHook.optionsContent}>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2, styles.optionsSubheader]}>{loc.wallets.details_address}</Text>
|
||||
<Text style={[styles.textValue, stylesHook.textValue, styles.addressSectionContent]} selectable>
|
||||
{(() => {
|
||||
try {
|
||||
await saveToDisk();
|
||||
} catch (error: any) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
console.error(error.message);
|
||||
return wallet.getAddress ? wallet.getAddress() : '';
|
||||
} catch (error: unknown) {
|
||||
return (error as Error).message;
|
||||
}
|
||||
}}
|
||||
})()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Stats */}
|
||||
<View style={[styles.detailsCard, stylesHook.detailsCard]}>
|
||||
<View style={styles.statsRow}>
|
||||
<View style={[styles.statsBox, stylesHook.statsBox]}>
|
||||
<View style={styles.statsBoxTitleRow}>
|
||||
<Text onPress={purgeTransactions} style={[styles.textLabel2, stylesHook.textLabel2]} testID="PurgeBackdoorButton">
|
||||
{loc.transactions.list_title}
|
||||
</Text>
|
||||
{walletTransactionsLength > 0 && (
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
shouldOpenOnLongPress={false}
|
||||
onPressMenuItem={toolTipOnPressMenuItem}
|
||||
actions={transactionsBoxMenuActions}
|
||||
>
|
||||
<Icon name="more-horiz" type="material" size={20} color={colors.alternativeTextColor} />
|
||||
</ToolTipMenu>
|
||||
)}
|
||||
</View>
|
||||
<BlueText style={[styles.statsBoxNumber, stylesHook.statsBoxNumber]}>{wallet.getTransactions().length}</BlueText>
|
||||
</View>
|
||||
{hasCoinControl && utxoCount !== null && utxoCount > 0 ? (
|
||||
<TouchableOpacity
|
||||
style={[styles.statsBox, stylesHook.statsBox]}
|
||||
onPress={() => navigate('SendDetailsRoot', { screen: 'CoinControl', params: { walletID } })}
|
||||
activeOpacity={0.8}
|
||||
testID="CoinsStatsBox"
|
||||
>
|
||||
<View style={styles.statsBoxTitleRow}>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_stats_coins}</Text>
|
||||
<View style={styles.statsBoxTitleRowSpacer} />
|
||||
</View>
|
||||
<BlueText style={[styles.statsBoxNumber, stylesHook.statsBoxNumber]}>{utxoCount}</BlueText>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<View style={[styles.statsBox, stylesHook.statsBox]} testID="CoinsStatsBox">
|
||||
<View style={styles.statsBoxTitleRow}>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_stats_coins}</Text>
|
||||
<View style={styles.statsBoxTitleRowSpacer} />
|
||||
</View>
|
||||
<BlueText style={[styles.statsBoxNumber, stylesHook.statsBoxNumber]}>
|
||||
{hasCoinControl && utxoCount !== null ? utxoCount : '—'}
|
||||
</BlueText>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Ark Address (Ark wallets only) */}
|
||||
{wallet.type === LightningArkWallet.type && (
|
||||
<View style={[styles.detailsCard, stylesHook.detailsCard]}>
|
||||
<View style={stylesHook.optionsContent}>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2, styles.optionsSubheader]}>
|
||||
{`Ark ${loc.wallets.details_address}`}
|
||||
</Text>
|
||||
<CopyTextToClipboard
|
||||
text={arkAddress}
|
||||
style={[styles.textValue, stylesHook.textValue, styles.addressSectionContent]}
|
||||
selectable
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
<>
|
||||
<Text onPress={purgeTransactions} style={[styles.textLabel2, stylesHook.textLabel2]} testID="PurgeBackdoorButton">
|
||||
{loc.transactions.transactions_count.toLowerCase()}
|
||||
</Text>
|
||||
<BlueText>{wallet.getTransactions().length}</BlueText>
|
||||
</>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{wallet.allowBIP47 && wallet.allowBIP47() ? (
|
||||
<>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.bip47.payment_code}</Text>
|
||||
<View style={styles.hardware}>
|
||||
<BlueText>{loc.bip47.purpose}</BlueText>
|
||||
<Switch
|
||||
value={isBIP47Enabled}
|
||||
onValueChange={async (value: boolean) => {
|
||||
setIsBIP47Enabled(value);
|
||||
if (wallet.switchBIP47) {
|
||||
wallet.switchBIP47(value);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
|
||||
}
|
||||
try {
|
||||
await saveToDisk();
|
||||
} catch (error: unknown) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
console.error((error as Error).message);
|
||||
}
|
||||
}}
|
||||
testID="BIP47Switch"
|
||||
{/* Show addresses & Contacts */}
|
||||
{(wallet instanceof AbstractHDElectrumWallet ||
|
||||
(wallet.type === WatchOnlyWallet.type && wallet.isHd && wallet.isHd()) ||
|
||||
isContactsVisible) && (
|
||||
<View style={[styles.detailsCard, stylesHook.detailsCard]}>
|
||||
<View style={stylesHook.optionsContent}>
|
||||
{(wallet instanceof AbstractHDElectrumWallet ||
|
||||
(wallet.type === WatchOnlyWallet.type && wallet.isHd && wallet.isHd())) && (
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
onPress={navigateToAddresses}
|
||||
title={loc.wallets.details_show_addresses}
|
||||
chevron
|
||||
bottomDivider={!!isContactsVisible}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
) : null}
|
||||
)}
|
||||
{isContactsVisible ? (
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
onPress={navigateToContacts}
|
||||
title={loc.bip47.contacts}
|
||||
chevron
|
||||
bottomDivider={false}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View>
|
||||
{/* Options container — header full width (single row so section background spans the card) */}
|
||||
<View style={[styles.detailsCard, stylesHook.detailsCard]}>
|
||||
<View
|
||||
style={[
|
||||
styles.sectionTitle,
|
||||
stylesHook.sectionTitle,
|
||||
styles.sectionTitleRowContainer,
|
||||
styles.optionsSectionHeader,
|
||||
stylesHook.optionsSectionHeader,
|
||||
]}
|
||||
>
|
||||
<BlueText style={[styles.sectionTitleText, stylesHook.sectionTitleText]}>{loc.wallets.details_options}</BlueText>
|
||||
</View>
|
||||
<View style={stylesHook.optionsContent}>
|
||||
{wallet.type === WatchOnlyWallet.type && wallet.isHd && wallet.isHd() && (
|
||||
<>
|
||||
<BlueSpacing10 />
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_advanced.toLowerCase()}</Text>
|
||||
<View style={styles.hardware}>
|
||||
<BlueText>{loc.wallets.details_use_with_hardware_wallet}</BlueText>
|
||||
<Switch
|
||||
value={walletUseWithHardwareWallet}
|
||||
onValueChange={async (value: boolean) => {
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2, styles.optionsSubheader]}>{loc.wallets.details_advanced}</Text>
|
||||
<ListItem
|
||||
containerStyle={styles.listItemContainerBorderLight}
|
||||
title={loc.wallets.details_use_with_hardware_wallet}
|
||||
switch={{
|
||||
value: walletUseWithHardwareWallet,
|
||||
onValueChange: async (value: boolean) => {
|
||||
setWalletUseWithHardwareWallet(value);
|
||||
if (wallet.setUseWithHardwareWalletEnabled) {
|
||||
wallet.setUseWithHardwareWalletEnabled(value);
|
||||
@ -597,80 +693,204 @@ const WalletDetails: React.FC = () => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
console.error((error as Error).message);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<View style={styles.row}>
|
||||
{wallet.allowMasterFingerprint && wallet.allowMasterFingerprint() && (
|
||||
<View style={styles.marginRight16}>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_master_fingerprint.toLowerCase()}</Text>
|
||||
{isMasterFingerPrintVisible ? (
|
||||
<BlueText selectable>{masterFingerprint ?? <ActivityIndicator />}</BlueText>
|
||||
) : (
|
||||
<TouchableOpacity onPress={onViewMasterFingerPrintPress}>
|
||||
<BlueText>{loc.multisig.view}</BlueText>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{derivationPath && (
|
||||
<View>
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.wallets.details_derivation_path}</Text>
|
||||
<BlueText selectable testID="DerivationPath">
|
||||
{derivationPath}
|
||||
</BlueText>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</BlueCard>
|
||||
{(wallet instanceof AbstractHDElectrumWallet || (wallet.type === WatchOnlyWallet.type && wallet.isHd && wallet.isHd())) && (
|
||||
<ListItem onPress={navigateToAddresses} title={loc.wallets.details_show_addresses} chevron />
|
||||
)}
|
||||
{isContactsVisible ? <ListItem onPress={navigateToContacts} title={loc.bip47.contacts} chevron /> : null}
|
||||
<BlueCard style={styles.address}>
|
||||
<View>
|
||||
<BlueSpacing20 />
|
||||
<Button onPress={navigateToWalletExport} testID="WalletExport" title={loc.wallets.details_export_backup} />
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
onPress={navigateToMultisigCoordinationSetup}
|
||||
testID="MultisigCoordinationSetup"
|
||||
title={loc.multisig.export_coordination_setup.replace(/^\w/, (c: string) => c.toUpperCase())}
|
||||
},
|
||||
}}
|
||||
bottomDivider
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
<Text onPress={exportInternals} style={[styles.textLabel2, stylesHook.textLabel2, styles.optionsSubheader]}>
|
||||
{loc.transactions.list_title}
|
||||
</Text>
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
title={loc.wallets.details_display}
|
||||
switch={{
|
||||
value: hideTransactionsInWalletsList,
|
||||
onValueChange: async (value: boolean) => {
|
||||
if (wallet.setHideTransactionsInWalletsList) {
|
||||
wallet.setHideTransactionsInWalletsList(!value);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
|
||||
setHideTransactionsInWalletsList(!wallet.getHideTransactionsInWalletsList());
|
||||
}
|
||||
try {
|
||||
await saveToDisk();
|
||||
} catch (error: any) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
console.error(error.message);
|
||||
}
|
||||
},
|
||||
}}
|
||||
bottomDivider
|
||||
/>
|
||||
{wallet.allowBIP47 && wallet.allowBIP47() && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
onPress={navigateToViewEditCosigners}
|
||||
testID="ViewEditCosigners"
|
||||
title={loc.multisig.view_edit_cosigners}
|
||||
<Text style={[styles.textLabel2, stylesHook.textLabel2, styles.optionsSubheader]}>{loc.bip47.payment_code}</Text>
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
title={loc.bip47.purpose}
|
||||
switch={{
|
||||
value: isBIP47Enabled,
|
||||
onValueChange: async (value: boolean) => {
|
||||
setIsBIP47Enabled(value);
|
||||
if (wallet.switchBIP47) {
|
||||
wallet.switchBIP47(value);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.ImpactLight);
|
||||
}
|
||||
try {
|
||||
await saveToDisk();
|
||||
} catch (error: unknown) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
console.error((error as Error).message);
|
||||
}
|
||||
},
|
||||
}}
|
||||
switchTestID="BIP47Switch"
|
||||
bottomDivider
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{wallet.allowXpub && wallet.allowXpub() && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton onPress={navigateToXPub} testID="XpubButton" title={loc.wallets.details_show_xpub} />
|
||||
</>
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
onPress={navigateToXPub}
|
||||
title={loc.wallets.details_show_xpub}
|
||||
chevron
|
||||
testID="XpubButton"
|
||||
bottomDivider
|
||||
/>
|
||||
)}
|
||||
{wallet.allowSignVerifyMessage && wallet.allowSignVerifyMessage() && (
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
onPress={navigateToSignVerify}
|
||||
title={loc.addresses.sign_title}
|
||||
chevron
|
||||
testID="SignVerify"
|
||||
bottomDivider={!!(wallet.type === MultisigHDWallet.type)}
|
||||
/>
|
||||
)}
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<SecondButton onPress={navigateToSignVerify} testID="SignVerify" title={loc.addresses.sign_title} />
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
onPress={navigateToMultisigCoordinationSetup}
|
||||
title={loc.multisig.export_coordination_setup.replace(/^\w/, (c: string) => c.toUpperCase())}
|
||||
chevron
|
||||
testID="MultisigCoordinationSetup"
|
||||
bottomDivider
|
||||
/>
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
onPress={navigateToViewEditCosigners}
|
||||
title={loc.multisig.view_edit_cosigners}
|
||||
chevron
|
||||
testID="ViewEditCosigners"
|
||||
bottomDivider={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Advanced */}
|
||||
<View style={[styles.detailsCard, stylesHook.detailsCard]}>
|
||||
<TouchableOpacity
|
||||
onPress={() => setIsAdvancedExpanded(prev => !prev)}
|
||||
style={[styles.sectionTitle, stylesHook.sectionTitle, styles.sectionTitleRowContainer]}
|
||||
activeOpacity={0.85}
|
||||
>
|
||||
<BlueText style={[styles.sectionTitleText, stylesHook.sectionTitleText]}>{loc.wallets.details_advanced}</BlueText>
|
||||
<Icon
|
||||
name={isAdvancedExpanded ? 'chevron-up' : 'chevron-down'}
|
||||
type="font-awesome"
|
||||
size={16}
|
||||
color={colors.alternativeTextColor}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
{isAdvancedExpanded && (
|
||||
<View style={[styles.advancedContent, stylesHook.advancedContent]}>
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
title={loc.wallets.details_type}
|
||||
titleStyle={stylesHook.advancedListItemTitle}
|
||||
rightTitle={wallet.typeReadable}
|
||||
rightTitleStyle={stylesHook.advancedListItemRightTitle}
|
||||
bottomDivider={
|
||||
!!(
|
||||
wallet.type === MultisigHDWallet.type ||
|
||||
derivationPath ||
|
||||
(wallet.allowMasterFingerprint && wallet.allowMasterFingerprint())
|
||||
)
|
||||
}
|
||||
/>
|
||||
{wallet.type === MultisigHDWallet.type && (
|
||||
<>
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
title={loc.wallets.details_multisig_type}
|
||||
titleStyle={stylesHook.advancedListItemTitle}
|
||||
rightTitle={`${wallet.getM()} / ${wallet.getN()} (${
|
||||
wallet.isNativeSegwit() ? 'native segwit' : wallet.isWrappedSegwit() ? 'wrapped segwit' : 'legacy'
|
||||
})`}
|
||||
rightTitleStyle={stylesHook.advancedListItemRightTitle}
|
||||
bottomDivider={!!(derivationPath || (wallet.allowMasterFingerprint && wallet.allowMasterFingerprint()))}
|
||||
/>
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
title={loc.multisig.how_many_signatures_can_bluewallet_make}
|
||||
titleStyle={stylesHook.advancedListItemTitle}
|
||||
rightTitle={String(wallet.howManySignaturesCanWeMake())}
|
||||
rightTitleStyle={stylesHook.advancedListItemRightTitle}
|
||||
bottomDivider={!!(derivationPath || (wallet.allowMasterFingerprint && wallet.allowMasterFingerprint()))}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{wallet.allowMasterFingerprint && wallet.allowMasterFingerprint() && (
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
onPress={isMasterFingerPrintVisible ? undefined : onViewMasterFingerPrintPress}
|
||||
title={loc.wallets.details_master_fingerprint}
|
||||
titleStyle={stylesHook.advancedListItemTitle}
|
||||
rightTitle={
|
||||
isMasterFingerPrintVisible ? (masterFingerprint ?? loc.wallets.import_derivation_loading) : loc.multisig.view
|
||||
}
|
||||
rightTitleStyle={stylesHook.advancedListItemRightTitle}
|
||||
bottomDivider={!!derivationPath}
|
||||
/>
|
||||
)}
|
||||
{derivationPath && (
|
||||
<ListItem
|
||||
containerStyle={stylesHook.listItemContainerBorder}
|
||||
title={loc.wallets.details_derivation_path}
|
||||
titleStyle={stylesHook.advancedListItemTitle}
|
||||
rightTitle={derivationPath}
|
||||
rightTitleStyle={stylesHook.advancedListItemRightTitle}
|
||||
bottomDivider={false}
|
||||
testID="DerivationPath"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<BlueCard style={styles.address}>
|
||||
<View>
|
||||
<SecondButton
|
||||
onPress={navigateToWalletExport}
|
||||
testID="WalletExport"
|
||||
title={loc.wallets.details_export_backup}
|
||||
backgroundColor={colors.mainColor}
|
||||
textColor={colors.buttonTextColor}
|
||||
/>
|
||||
<BlueSpacing20 />
|
||||
<BlueSpacing20 />
|
||||
<SecondButton
|
||||
onPress={handleDeleteButtonTapped}
|
||||
testID="DeleteWallet"
|
||||
title={loc.wallets.details_delete_wallet}
|
||||
backgroundColor={colors.redBG}
|
||||
textColor={colors.redText}
|
||||
/>
|
||||
</View>
|
||||
</BlueCard>
|
||||
</>
|
||||
@ -685,6 +905,11 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
addressSectionContent: {
|
||||
paddingTop: 4,
|
||||
paddingBottom: 16,
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
textLabel1: {
|
||||
fontWeight: '500',
|
||||
fontSize: 14,
|
||||
@ -693,37 +918,101 @@ const styles = StyleSheet.create({
|
||||
textLabel2: {
|
||||
fontWeight: '500',
|
||||
fontSize: 14,
|
||||
marginVertical: 16,
|
||||
marginVertical: 8,
|
||||
},
|
||||
textValue: {
|
||||
fontWeight: '500',
|
||||
fontSize: 14,
|
||||
},
|
||||
input: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
borderBottomWidth: 0.5,
|
||||
minHeight: 44,
|
||||
height: 44,
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
nameRow: {
|
||||
marginBottom: 32,
|
||||
},
|
||||
inputText: {
|
||||
nameValue: {
|
||||
flex: 1,
|
||||
marginHorizontal: 8,
|
||||
minHeight: 33,
|
||||
color: '#81868e',
|
||||
fontWeight: '500',
|
||||
fontSize: 18,
|
||||
},
|
||||
hardware: {
|
||||
flexDirection: 'row',
|
||||
editButton: {
|
||||
paddingVertical: 4,
|
||||
paddingHorizontal: 12,
|
||||
borderRadius: 6,
|
||||
minWidth: 50,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
row: {
|
||||
editButtonText: {
|
||||
fontSize: 15,
|
||||
fontWeight: '500',
|
||||
lineHeight: 20,
|
||||
},
|
||||
detailsCard: {
|
||||
marginHorizontal: 16,
|
||||
marginBottom: 40,
|
||||
padding: 0,
|
||||
borderRadius: 12,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
sectionTitle: {
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 16,
|
||||
borderTopLeftRadius: 12,
|
||||
borderTopRightRadius: 12,
|
||||
},
|
||||
sectionTitleRowContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
marginRight16: {
|
||||
marginRight: 16,
|
||||
sectionTitleText: {
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
},
|
||||
optionsSubheader: {
|
||||
paddingTop: 8,
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
statsRow: {
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
statsBox: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
},
|
||||
statsBoxTitleRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
statsBoxTitleRowSpacer: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
optionsSectionHeader: {
|
||||
width: '100%',
|
||||
alignSelf: 'stretch',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 0,
|
||||
minHeight: 44,
|
||||
borderTopLeftRadius: 12,
|
||||
borderTopRightRadius: 12,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
statsBoxNumber: {
|
||||
fontSize: 32,
|
||||
fontWeight: '700',
|
||||
},
|
||||
advancedContent: {
|
||||
marginTop: 0,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
},
|
||||
listItemContainerBorderLight: {
|
||||
backgroundColor: 'transparent',
|
||||
borderBottomColor: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -120,17 +120,15 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
// network -> electrum server
|
||||
// change electrum server to electrum.blockstream.info and revert it back
|
||||
// skip this test on iOS. HeaderMenuButton tap triggers a keyboard open for some reason.
|
||||
if (device.getPlatform() === 'andoid') {
|
||||
if (device.getPlatform() === 'android') {
|
||||
await element(by.id('ElectrumSettings')).tap();
|
||||
await waitFor(element(by.id('HostInput')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('ElectrumSettingsScrollView'))
|
||||
.scroll(500, 'down'); // in case emu screen is small and it doesnt fit
|
||||
await element(by.id('HostInput')).replaceText('electrum.blockstream.info\n');
|
||||
await element(by.id('HostInput')).tapReturnKey();
|
||||
await waitForKeyboardToClose();
|
||||
await element(by.id('PortInput')).replaceText('50001\n');
|
||||
await element(by.id('PortInput')).tapReturnKey();
|
||||
await waitForKeyboardToClose();
|
||||
await waitFor(element(by.id('Save')))
|
||||
.toBeVisible()
|
||||
@ -263,12 +261,6 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
await tapAndTapAgainIfElementIsNotVisible('cr34t3d', 'ReceiveButton');
|
||||
await element(by.id('ReceiveButton')).tap();
|
||||
await element(by.text('Yes, I have.')).tap();
|
||||
try {
|
||||
// in case emulator has no google services and doesnt support pushes
|
||||
// we just dont show this popup
|
||||
await element(by.text(`No, and do not ask me again.`)).tap();
|
||||
await element(by.text(`No, and do not ask me again.`)).tap(); // sometimes the first click doesnt work (detox issue, not app's)
|
||||
} catch (_) {}
|
||||
await waitForId('BitcoinAddressQRCode');
|
||||
await waitForId('CopyTextToClipboard');
|
||||
await element(by.id('SetCustomAmountButton')).tap();
|
||||
@ -584,18 +576,20 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
await element(by.id('Multisig Vault')).tap(); // go inside the wallet
|
||||
await waitForId('ReceiveButton');
|
||||
await element(by.id('ReceiveButton')).tap();
|
||||
try {
|
||||
// in case emulator has no google services and doesnt support pushes
|
||||
// we just dont show this popup
|
||||
await element(by.text(`No, and do not ask me again.`)).tap();
|
||||
await element(by.text(`No, and do not ask me again.`)).tap(); // sometimes the first click doesnt work (detox issue, not app's)
|
||||
} catch (_) {}
|
||||
|
||||
await waitForLabel('bc1qmf06nt4jhvzz4387ak8fecs42k6jqygr2unumetfc7xkdup7ah9s8phlup');
|
||||
await goBack();
|
||||
|
||||
await element(by.id('WalletDetails')).tap();
|
||||
await waitForText('2 / 2 (native segwit)');
|
||||
await waitFor(element(by.text('Advanced')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(150, 'down');
|
||||
await element(by.text('Advanced')).tap();
|
||||
await waitFor(element(by.text('2 / 2 (native segwit)')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(100, 'down');
|
||||
|
||||
process.env.CI && require('fs').writeFileSync(lockFile, '1');
|
||||
});
|
||||
@ -776,7 +770,15 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
// go to wallet and check derivation path
|
||||
await element(by.id('Imported HD Legacy (BIP44 P2PKH)')).tap();
|
||||
await element(by.id('WalletDetails')).tap();
|
||||
await expect(element(by.id('DerivationPath'))).toHaveText("m/44'/0'/0'");
|
||||
await waitFor(element(by.text('Advanced')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(150, 'down');
|
||||
await element(by.text('Advanced')).tap();
|
||||
await waitFor(element(by.text("m/44'/0'/0'")))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(100, 'down');
|
||||
process.env.CI && require('fs').writeFileSync(lockFile, '1');
|
||||
});
|
||||
|
||||
@ -938,14 +940,23 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
|
||||
// verify wallet details
|
||||
await element(by.id('WalletDetails')).tap();
|
||||
await waitForText('2 / 3 (native segwit)');
|
||||
await waitFor(element(by.text('Advanced')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(150, 'down');
|
||||
await element(by.text('Advanced')).tap();
|
||||
await waitFor(element(by.text('2 / 3 (native segwit)')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(100, 'down');
|
||||
|
||||
// test Export Coordination Setup, it has animated qrcode, that uses setInterval, so we need to disable synchronization
|
||||
await waitFor(element(by.id('MultisigCoordinationSetup')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(150, 'down');
|
||||
await element(by.id('MultisigCoordinationSetup')).tap();
|
||||
// Tap high in the row: full-width list hitboxes near the bottom edge can extend into the system nav bar on Android.
|
||||
await element(by.id('MultisigCoordinationSetup')).tap({ x: 120, y: 18 });
|
||||
await device.disableSynchronization();
|
||||
await waitForId('ExportMultisigCoordinationSetupView');
|
||||
await element(by.id('NavigationCloseButton')).atIndex(0).tap();
|
||||
@ -959,7 +970,11 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
const vaultReceiveAddress = await extractTextFromElementById('AddressValue');
|
||||
assert.ok(vaultReceiveAddress && vaultReceiveAddress.length > 20);
|
||||
await goBack();
|
||||
await waitForId('ReceiveButton');
|
||||
await element(by.id('WalletDetails')).tap();
|
||||
await waitFor(element(by.id('WalletDetailsScroll')))
|
||||
.toBeVisible()
|
||||
.withTimeout(20000);
|
||||
|
||||
console.log('vaultReceiveAddress', vaultReceiveAddress);
|
||||
|
||||
@ -968,6 +983,8 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(100, 'down');
|
||||
// Extra scroll moves the full-width row up so its hitbox no longer overlaps the Android system nav bar.
|
||||
await element(by.id('WalletDetailsScroll')).scroll(200, 'down');
|
||||
await element(by.id('ViewEditCosigners')).tap();
|
||||
await waitForText('Vault Key 1');
|
||||
await expect(element(by.text('Vault Key 2'))).toBeVisible();
|
||||
@ -1008,11 +1025,17 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
|
||||
// go back to manage keys, restore seed for cosigner 3, and save
|
||||
await goBack();
|
||||
await waitForId('ReceiveButton');
|
||||
await element(by.id('WalletDetails')).tap();
|
||||
await waitFor(element(by.id('WalletDetailsScroll')))
|
||||
.toBeVisible()
|
||||
.withTimeout(20000);
|
||||
await waitFor(element(by.id('ViewEditCosigners')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(100, 'down');
|
||||
// Extra scroll moves the full-width row up so its hitbox no longer overlaps the Android system nav bar.
|
||||
await element(by.id('WalletDetailsScroll')).scroll(200, 'down');
|
||||
await element(by.id('ViewEditCosigners')).tap();
|
||||
await waitFor(element(by.id('VaultCosignerImportMnemonics3')))
|
||||
.toBeVisible()
|
||||
@ -1096,7 +1119,15 @@ describe('BlueWallet UI Tests - no wallets', () => {
|
||||
await element(by.id('Multisig Vault')).tap();
|
||||
await waitForId('ReceiveButton');
|
||||
await element(by.id('WalletDetails')).tap();
|
||||
await waitForText('2 / 2 (wrapped segwit)');
|
||||
await waitFor(element(by.text('Advanced')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(150, 'down');
|
||||
await element(by.text('Advanced')).tap();
|
||||
await waitFor(element(by.text('2 / 2 (wrapped segwit)')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(100, 'down');
|
||||
|
||||
process.env.CI && require('fs').writeFileSync(lockFile, '1');
|
||||
});
|
||||
|
||||
@ -394,12 +394,16 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
||||
// switch on BIP47 slider if its not switched
|
||||
if (!(await getSwitchValue('BIP47Switch'))) {
|
||||
await expect(element(by.text('Contacts'))).not.toBeVisible();
|
||||
// Scroll down so Options section (BIP47 switch) is on screen
|
||||
await element(by.id('WalletDetailsScroll')).swipe('up', 'fast', 1);
|
||||
await element(by.id('WalletDetailsScroll')).swipe('up', 'fast', 1);
|
||||
await waitFor(element(by.id('BIP47Switch')))
|
||||
.toExist()
|
||||
.withTimeout(5000);
|
||||
await element(by.id('BIP47Switch')).tap();
|
||||
await waitFor(element(by.text('Contacts')))
|
||||
.toBeVisible()
|
||||
.whileElement(by.id('WalletDetailsScroll'))
|
||||
.scroll(500, 'down');
|
||||
await expect(element(by.text('Contacts'))).toBeVisible();
|
||||
.toExist()
|
||||
.withTimeout(10000);
|
||||
await goBack();
|
||||
} else {
|
||||
await goBack();
|
||||
@ -408,11 +412,6 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
||||
// go to receive screen and check that payment code is there
|
||||
await waitForId('ReceiveButton');
|
||||
await element(by.id('ReceiveButton')).tap();
|
||||
|
||||
try {
|
||||
await element(by.text('ASK ME LATER.')).tap();
|
||||
} catch (_) {}
|
||||
|
||||
await element(by.text('Payment Code')).tap();
|
||||
await element(by.id('ReceiveDetailsScrollView')).swipe('up', 'fast', 1); // in case emu screen is small and it doesnt fit
|
||||
await sleep(200);
|
||||
@ -562,9 +561,10 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
||||
// let's test wallet details screens
|
||||
await element(by.id('WalletDetails')).tap();
|
||||
|
||||
// rename test
|
||||
await element(by.id('WalletNameInput')).replaceText('testname');
|
||||
await element(by.id('WalletNameInput')).typeText('\n'); // newline is what triggers saving the wallet
|
||||
// rename test: tap edit, enter new name in prompt, tap OK
|
||||
await element(by.id('WalletNameEditButton')).tap();
|
||||
await typeTextIntoAlertInput('testname');
|
||||
await element(by.text('OK')).tap();
|
||||
await waitForKeyboardToClose();
|
||||
await goBack();
|
||||
await waitForText('testname');
|
||||
@ -572,8 +572,9 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
|
||||
await element(by.id('WalletDetails')).tap();
|
||||
|
||||
// rename back
|
||||
await element(by.id('WalletNameInput')).replaceText('Imported HD SegWit (BIP84 Bech32 Native)');
|
||||
await element(by.id('WalletNameInput')).typeText('\n'); // newline is what triggers saving the wallet
|
||||
await element(by.id('WalletNameEditButton')).tap();
|
||||
await typeTextIntoAlertInput('Imported HD SegWit (BIP84 Bech32 Native)');
|
||||
await element(by.text('OK')).tap();
|
||||
await waitForKeyboardToClose();
|
||||
await goBack();
|
||||
await waitForText('Imported HD SegWit (BIP84 Bech32 Native)');
|
||||
|
||||
@ -51,12 +51,6 @@ describe('BlueWallet UI Tests - import Watch-only wallet (zpub)', () => {
|
||||
} catch (_) {}
|
||||
|
||||
await element(by.id('ReceiveButton')).tap();
|
||||
try {
|
||||
// in case emulator has no google services and doesnt support pushes
|
||||
// we just dont show this popup
|
||||
await element(by.text(`No, and do not ask me again.`)).tap();
|
||||
await element(by.text(`No, and do not ask me again.`)).tap(); // sometimes the first click doesnt work (detox issue, not app's)
|
||||
} catch (_) {}
|
||||
await expect(element(by.id('BitcoinAddressQRCode'))).toBeVisible();
|
||||
await expect(element(by.label('bc1qgrhr5xc5774maph97d73ydrjlqqmg2v6jjlr29'))).toBeVisible();
|
||||
await element(by.id('SetCustomAmountButton')).tap();
|
||||
|
||||
@ -166,8 +166,7 @@ export async function helperDeleteWallet(label, remainingBalanceSat = false) {
|
||||
await element(by.id('WalletDetails')).tap();
|
||||
await element(by.id('WalletDetailsScroll')).swipe('up', 'fast', 1);
|
||||
await sleep(200);
|
||||
await element(by.id('HeaderMenuButton')).tap();
|
||||
await element(by.text('Delete')).tap();
|
||||
await element(by.id('DeleteWallet')).tap();
|
||||
await waitForText('Yes, delete');
|
||||
await element(by.text('Yes, delete')).tap();
|
||||
if (remainingBalanceSat) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user