ref: prompt

This commit is contained in:
Ivan Vershigora 2026-05-21 13:29:23 +01:00
parent 431a8006ea
commit c8344e6037
No known key found for this signature in database
GPG Key ID: DCCF7FB5ED2CEBD7
9 changed files with 68 additions and 95 deletions

View File

@ -23,7 +23,7 @@ export const startAndDecrypt = async (retry?: boolean, passwordPrompt?: Password
password = await passwordPrompt();
} else {
do {
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, false);
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, { cancelable: false });
} while (!password);
}
}

View File

@ -2,15 +2,18 @@ import { Platform } from 'react-native';
import prompt from 'react-native-prompt-android';
import loc from '../loc';
export default (
title: string,
text: string,
isCancelable = true,
type: PromptType | PromptTypeIOS | PromptTypeAndroid = 'secure-text',
isOKDestructive = false,
continueButtonText = loc._.ok,
defaultInputValue?: string,
): Promise<string> => {
type PromptHelperOptions = {
cancelable?: boolean;
type?: PromptType | PromptTypeIOS | PromptTypeAndroid;
destructive?: boolean; // applies only to the cancelable (two-button) layout
continueButtonText?: string;
defaultValue?: string;
};
export default (title: string, text: string, options: PromptHelperOptions = {}): Promise<string> => {
const { cancelable = true, destructive = false, continueButtonText = loc._.ok, defaultValue } = options;
let { type = 'secure-text' } = options;
const keyboardType = type === 'numeric' ? 'numeric' : 'default';
if (Platform.OS === 'ios' && type === 'numeric') {
@ -19,7 +22,7 @@ export default (
}
return new Promise((resolve, reject) => {
const buttons: Array<PromptButton> = isCancelable
const buttons: Array<PromptButton> = cancelable
? [
{
text: loc._.cancel,
@ -34,7 +37,7 @@ export default (
console.log('OK Pressed');
resolve(password);
},
style: isOKDestructive ? 'destructive' : 'default',
style: destructive ? 'destructive' : 'default',
},
]
: [
@ -47,13 +50,12 @@ export default (
},
];
const message = defaultInputValue !== undefined ? '' : text;
const message = defaultValue !== undefined ? '' : text;
prompt(title, message, buttons, {
type,
cancelable: isCancelable,
// @ts-ignore suppressed because its supported only on ios and is absent from type definitions
cancelable,
keyboardType,
...(defaultInputValue !== undefined && { defaultValue: defaultInputValue }),
...(defaultValue !== undefined && { defaultValue }),
});
});
};

View File

@ -140,7 +140,7 @@ const LnurlPay: React.FC = () => {
try {
let comment: string | undefined;
if (_LN.getCommentAllowed()) {
comment = await prompt('Comment', '', false, 'plain-text');
comment = await prompt('Comment', '', { cancelable: false, type: 'plain-text' });
}
const bolt11payload = await _LN.requestBolt11FromLnurlPayService(amountSats, comment);

View File

@ -677,7 +677,7 @@ const TransactionStatus: React.FC = () => {
const handleNotePress = useCallback(async () => {
const currentMemo = txMetadata[tx.hash]?.memo || '';
try {
const newMemo = await prompt(loc.send.details_note_placeholder, '', true, 'plain-text', false, undefined, currentMemo);
const newMemo = await prompt(loc.send.details_note_placeholder, '', { type: 'plain-text', defaultValue: currentMemo });
if (newMemo !== undefined) {
txMetadata[tx.hash] = { memo: newMemo };
await saveToDisk();

View File

@ -134,7 +134,7 @@ export default function PaymentCodesList() {
break;
}
case String(Actions.rename): {
const newName = await prompt(loc.bip47.rename, loc.bip47.provide_name, true, 'plain-text');
const newName = await prompt(loc.bip47.rename, loc.bip47.provide_name, { type: 'plain-text' });
if (!newName) return;
counterpartyMetadata[pc] = { label: newName };
@ -245,7 +245,7 @@ export default function PaymentCodesList() {
const onAddContactPress = async () => {
try {
const newPc = await prompt(loc.bip47.add_contact, loc.bip47.provide_payment_code, true, 'plain-text');
const newPc = await prompt(loc.bip47.add_contact, loc.bip47.provide_payment_code, { type: 'plain-text' });
if (!newPc) return;
await _addContact(newPc);

View File

@ -74,6 +74,7 @@ const WalletDetails: React.FC = () => {
const [masterFingerprint, setMasterFingerprint] = useState<string | undefined>();
const [arkAddress, setArkAddress] = useState<string>('');
const [walletName, setWalletName] = useState<string>(wallet.getLabel());
const walletTransactionsLength = useMemo<number>(() => wallet.getTransactions().length, [wallet]);
const [coinControlStats, setCoinControlStats] = useState(() => getCoinControlStats(wallet));
@ -157,10 +158,7 @@ const WalletDetails: React.FC = () => {
const walletBalanceConfirmation = await prompt(
loc.wallets.details_delete_wallet,
loc.formatString(loc.wallets.details_del_wb_q, { balance }),
true,
'numeric',
true,
loc.wallets.details_delete,
{ type: 'numeric', destructive: true, continueButtonText: loc.wallets.details_delete },
);
// Remove any non-numeric characters before comparison
const cleanedConfirmation = (walletBalanceConfirmation || '').replace(/[^0-9]/g, '');
@ -485,15 +483,26 @@ const WalletDetails: React.FC = () => {
};
const handleEditWalletName = useCallback(async () => {
let newName: string;
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();
newName = await prompt(loc.wallets.add_wallet_name, '', { type: 'plain-text', defaultValue: wallet.getLabel() });
} catch (_) {
// User cancelled
return;
}
const trimmed = newName.trim();
if (trimmed.length === 0) return;
const previousLabel = wallet.getLabel();
if (previousLabel === trimmed) return;
wallet.setLabel(trimmed);
setWalletName(trimmed);
try {
await saveToDisk();
} catch (error: unknown) {
wallet.setLabel(previousLabel);
setWalletName(previousLabel);
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: error instanceof Error ? error.message : String(error) });
}
}, [wallet, saveToDisk]);
@ -519,7 +528,7 @@ const WalletDetails: React.FC = () => {
ellipsizeMode="tail"
testID="WalletNameDisplay"
>
{wallet.getLabel()}
{walletName}
</Text>
<TouchableOpacity
style={[styles.editButton, stylesHook.editButton]}

View File

@ -286,7 +286,7 @@ const WalletsAddMultisigStep2 = () => {
// do nothing, it's already set
} else {
try {
fp = await prompt(loc.multisig.input_fp, loc.multisig.input_fp_explain, true, 'plain-text');
fp = await prompt(loc.multisig.input_fp, loc.multisig.input_fp_explain, { type: 'plain-text' });
fp = (fp + '').toUpperCase();
if (!MultisigHDWallet.isFpValid(fp)) fp = '00000000';
} catch (e) {
@ -297,12 +297,9 @@ const WalletsAddMultisigStep2 = () => {
// do nothing, it's already set
} else {
try {
path = await prompt(
loc.multisig.input_path,
loc.formatString(loc.multisig.input_path_explain, { default: getPath() }),
true,
'plain-text',
);
path = await prompt(loc.multisig.input_path, loc.formatString(loc.multisig.input_path_explain, { default: getPath() }), {
type: 'plain-text',
});
if (!MultisigHDWallet.isPathValid(path)) path = getPath();
} catch {
return setIsLoading(false);

View File

@ -270,15 +270,10 @@ describe('TransactionStatus regression', () => {
expect(mockPrompt).toHaveBeenCalledTimes(1);
});
// 7th argument is defaultInputValue: current memo should be in the input field, not in the message
expect(mockPrompt).toHaveBeenCalledWith(
'Note to Self',
'', // message empty so content is not in alert body
true,
'plain-text',
false,
undefined,
existingMemo, // defaultInputValue: pre-fill input for easy editing
{ type: 'plain-text', defaultValue: existingMemo }, // defaultValue: pre-fill input for easy editing
);
});
});

View File

@ -1,57 +1,27 @@
// Type definitions for react-native-prompt-android 0.3.1
// Project: https://github.com/shimohq/react-native-prompt-android
// Definitions by: Krystof Celba <https://github.com/krystofcelba>
// TypeScript Version: 2.6.1
// Supplemental types for `react-native-prompt-android`.
// The package ships its own `index.d.ts`; this file only adds the type aliases
// used unqualified across the app, plus a `keyboardType` augmentation that is
// absent from the upstream definitions.
type PromptButton = {
text?: string;
onPress?: (message: string) => void;
import type { KeyboardTypeOptions } from 'react-native';
/** @platform ios */
style?: 'default' | 'cancel' | 'destructive';
};
declare global {
type PromptButton = {
text?: string;
onPress?: (message: string) => void;
type PromptType = 'default' | 'plain-text' | 'secure-text';
type PromptTypeIOS = 'login-password';
type PromptTypeAndroid = 'numeric' | 'email-address' | 'phone-pad';
/** @platform ios */
style?: 'default' | 'cancel' | 'destructive';
};
type PromptStyleAndroid = 'default' | 'shimo';
interface PromptOptions {
/**
* * Cross platform:
*
* - `'default'`
* - `'plain-text'`
* - `'secure-text'`
*
* * iOS only:
*
* - `'login-password'`
*
* * Android only:
*
* - `'numeric'`
* - `'email-address'`
* - `'phone-pad'`
*/
type?: PromptType | PromptTypeIOS | PromptTypeAndroid;
defaultValue?: string;
/** @platform android */
placeholder?: string;
/** @platform android */
cancelable?: boolean;
/** @platform android */
style?: PromptStyleAndroid;
type PromptType = 'default' | 'plain-text' | 'secure-text';
type PromptTypeIOS = 'login-password';
type PromptTypeAndroid = 'numeric' | 'email-address' | 'phone-pad';
}
declare function prompt(
title?: string,
message?: string,
callbackOrButtons?: ((value: string) => void) | Array<PromptButton>,
options?: PromptOptions,
): void;
// `keyboardType` is supported by the native prompt but is absent from the upstream type definitions.
declare module 'react-native-prompt-android' {
interface PromptOptions {
keyboardType?: KeyboardTypeOptions;
}
}