From c8344e60376777b7d08559116ae1489eee0ed356 Mon Sep 17 00:00:00 2001 From: Ivan Vershigora Date: Thu, 21 May 2026 13:29:23 +0100 Subject: [PATCH] ref: prompt --- blue_modules/start-and-decrypt.ts | 2 +- helpers/prompt.ts | 32 +++++----- screen/lnd/lnurlPay.tsx | 2 +- screen/transactions/TransactionStatus.tsx | 2 +- screen/wallets/PaymentCodesList.tsx | 4 +- screen/wallets/WalletDetails.tsx | 31 ++++++---- screen/wallets/addMultisigStep2.tsx | 11 ++-- tests/unit/transaction-status.test.tsx | 7 +-- typings/react-native-prompt-android.d.ts | 72 +++++++---------------- 9 files changed, 68 insertions(+), 95 deletions(-) diff --git a/blue_modules/start-and-decrypt.ts b/blue_modules/start-and-decrypt.ts index 47fa97146..d37716794 100644 --- a/blue_modules/start-and-decrypt.ts +++ b/blue_modules/start-and-decrypt.ts @@ -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); } } diff --git a/helpers/prompt.ts b/helpers/prompt.ts index 099ad71d5..be1642498 100644 --- a/helpers/prompt.ts +++ b/helpers/prompt.ts @@ -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 => { +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 => { + 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 = isCancelable + const buttons: Array = 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 }), }); }); }; diff --git a/screen/lnd/lnurlPay.tsx b/screen/lnd/lnurlPay.tsx index d64e571ea..a4d58eafb 100644 --- a/screen/lnd/lnurlPay.tsx +++ b/screen/lnd/lnurlPay.tsx @@ -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); diff --git a/screen/transactions/TransactionStatus.tsx b/screen/transactions/TransactionStatus.tsx index 15b07f6df..ea1515ed9 100644 --- a/screen/transactions/TransactionStatus.tsx +++ b/screen/transactions/TransactionStatus.tsx @@ -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(); diff --git a/screen/wallets/PaymentCodesList.tsx b/screen/wallets/PaymentCodesList.tsx index 5915b40fa..22d4251b6 100644 --- a/screen/wallets/PaymentCodesList.tsx +++ b/screen/wallets/PaymentCodesList.tsx @@ -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); diff --git a/screen/wallets/WalletDetails.tsx b/screen/wallets/WalletDetails.tsx index 88f1ad25d..ec769b4a9 100644 --- a/screen/wallets/WalletDetails.tsx +++ b/screen/wallets/WalletDetails.tsx @@ -74,6 +74,7 @@ const WalletDetails: React.FC = () => { const [masterFingerprint, setMasterFingerprint] = useState(); const [arkAddress, setArkAddress] = useState(''); + const [walletName, setWalletName] = useState(wallet.getLabel()); const walletTransactionsLength = useMemo(() => 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} { // 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); diff --git a/tests/unit/transaction-status.test.tsx b/tests/unit/transaction-status.test.tsx index 4b1129f12..d178132b6 100644 --- a/tests/unit/transaction-status.test.tsx +++ b/tests/unit/transaction-status.test.tsx @@ -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 ); }); }); diff --git a/typings/react-native-prompt-android.d.ts b/typings/react-native-prompt-android.d.ts index 52ae81499..d0d65b955 100644 --- a/typings/react-native-prompt-android.d.ts +++ b/typings/react-native-prompt-android.d.ts @@ -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 -// 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, - 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; + } +}