mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-01-19 05:45:15 +01:00
REF: Use tooltip menu for SendDetails on Android
This commit is contained in:
parent
47acf76efb
commit
3616d90b90
@ -36,7 +36,7 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
|||||||
actionKey: action.id.toString(),
|
actionKey: action.id.toString(),
|
||||||
actionTitle: action.text,
|
actionTitle: action.text,
|
||||||
icon: action.icon?.iconValue ? ({ iconType: 'SYSTEM', iconValue: action.icon.iconValue } as IconConfig) : undefined,
|
icon: action.icon?.iconValue ? ({ iconType: 'SYSTEM', iconValue: action.icon.iconValue } as IconConfig) : undefined,
|
||||||
state: action.menuStateOn ? ('on' as MenuState) : ('off' as MenuState),
|
state: action.menuState ?? undefined,
|
||||||
attributes: action.disabled ? ['disabled'] : [],
|
attributes: action.disabled ? ['disabled'] : [],
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
@ -46,8 +46,7 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
|
|||||||
return {
|
return {
|
||||||
id: action.id.toString(),
|
id: action.id.toString(),
|
||||||
title: action.text,
|
title: action.text,
|
||||||
image: action.menuStateOn && Platform.OS === 'android' ? 'checkbox_on_background' : action.icon?.iconValue || undefined,
|
state: action.menuState === undefined ? undefined : ((action.menuState ? 'on' : 'off') as MenuState),
|
||||||
state: action.menuStateOn ? ('on' as MenuState) : undefined,
|
|
||||||
attributes: { disabled: action.disabled },
|
attributes: { disabled: action.disabled },
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -7,7 +7,7 @@ export interface Action {
|
|||||||
iconValue: string;
|
iconValue: string;
|
||||||
};
|
};
|
||||||
menuTitle?: string;
|
menuTitle?: string;
|
||||||
menuStateOn?: boolean;
|
menuState?: 'mixed' | boolean | undefined;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
displayInline?: boolean;
|
displayInline?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
"close": "Close",
|
"close": "Close",
|
||||||
"change_input_currency": "Change input currency",
|
"change_input_currency": "Change input currency",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"more": "More",
|
|
||||||
"pick_image": "Choose image from library",
|
"pick_image": "Choose image from library",
|
||||||
"pick_file": "Choose a file",
|
"pick_file": "Choose a file",
|
||||||
"enter_amount": "Enter amount",
|
"enter_amount": "Enter amount",
|
||||||
|
@ -19,7 +19,6 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
TouchableWithoutFeedback,
|
|
||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import DocumentPicker from 'react-native-document-picker';
|
import DocumentPicker from 'react-native-document-picker';
|
||||||
@ -36,11 +35,10 @@ import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electr
|
|||||||
import AddressInput from '../../components/AddressInput';
|
import AddressInput from '../../components/AddressInput';
|
||||||
import presentAlert from '../../components/Alert';
|
import presentAlert from '../../components/Alert';
|
||||||
import AmountInput from '../../components/AmountInput';
|
import AmountInput from '../../components/AmountInput';
|
||||||
import BottomModal, { BottomModalHandle } from '../../components/BottomModal';
|
import { BottomModalHandle } from '../../components/BottomModal';
|
||||||
import Button from '../../components/Button';
|
import Button from '../../components/Button';
|
||||||
import CoinsSelected from '../../components/CoinsSelected';
|
import CoinsSelected from '../../components/CoinsSelected';
|
||||||
import InputAccessoryAllFunds from '../../components/InputAccessoryAllFunds';
|
import InputAccessoryAllFunds from '../../components/InputAccessoryAllFunds';
|
||||||
import ListItem from '../../components/ListItem';
|
|
||||||
import { useTheme } from '../../components/themes';
|
import { useTheme } from '../../components/themes';
|
||||||
import ToolTipMenu from '../../components/TooltipMenu';
|
import ToolTipMenu from '../../components/TooltipMenu';
|
||||||
import { requestCameraAuthorization, scanQrHelper } from '../../helpers/scan-qr';
|
import { requestCameraAuthorization, scanQrHelper } from '../../helpers/scan-qr';
|
||||||
@ -95,7 +93,7 @@ const SendDetails = () => {
|
|||||||
const optionsModalRef = useRef<BottomModalHandle>(null);
|
const optionsModalRef = useRef<BottomModalHandle>(null);
|
||||||
const [walletSelectionOrCoinsSelectedHidden, setWalletSelectionOrCoinsSelectedHidden] = useState(false);
|
const [walletSelectionOrCoinsSelectedHidden, setWalletSelectionOrCoinsSelectedHidden] = useState(false);
|
||||||
const [isAmountToolbarVisibleForAndroid, setIsAmountToolbarVisibleForAndroid] = useState(false);
|
const [isAmountToolbarVisibleForAndroid, setIsAmountToolbarVisibleForAndroid] = useState(false);
|
||||||
const [isTransactionReplaceable, setIsTransactionReplaceable] = useState<boolean>(false);
|
const [isTransactionReplaceable, setIsTransactionReplaceable] = useState<boolean | undefined>(false);
|
||||||
const [addresses, setAddresses] = useState<IPaymentDestinations[]>([]);
|
const [addresses, setAddresses] = useState<IPaymentDestinations[]>([]);
|
||||||
const [units, setUnits] = useState<BitcoinUnit[]>([]);
|
const [units, setUnits] = useState<BitcoinUnit[]>([]);
|
||||||
const [transactionMemo, setTransactionMemo] = useState<string>('');
|
const [transactionMemo, setTransactionMemo] = useState<string>('');
|
||||||
@ -131,15 +129,11 @@ const SendDetails = () => {
|
|||||||
return initialFee;
|
return initialFee;
|
||||||
}, [customFee, feePrecalc, networkTransactionFees]);
|
}, [customFee, feePrecalc, networkTransactionFees]);
|
||||||
|
|
||||||
useEffect(() => {
|
const onReplaceableFeeSwitchValueChanged = (value: boolean) => {
|
||||||
console.log('send/details - useEffect');
|
setIsTransactionReplaceable(value);
|
||||||
if (wallet) {
|
};
|
||||||
setHeaderRightOptions();
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [colors, wallet, isTransactionReplaceable, balance, addresses, isEditable, isLoading]);
|
|
||||||
|
|
||||||
// keyboad effects
|
// keyboard effects
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const _keyboardDidShow = () => {
|
const _keyboardDidShow = () => {
|
||||||
setWalletSelectionOrCoinsSelectedHidden(true);
|
setWalletSelectionOrCoinsSelectedHidden(true);
|
||||||
@ -235,8 +229,7 @@ const SendDetails = () => {
|
|||||||
} else {
|
} else {
|
||||||
setAddresses([{ address: '', key: String(Math.random()) } as IPaymentDestinations]); // key is for the FlatList
|
setAddresses([{ address: '', key: String(Math.random()) } as IPaymentDestinations]); // key is for the FlatList
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [routeParams.uri, routeParams.address, routeParams.addRecipientParams, addresses, routeParams, setParams]);
|
||||||
}, [routeParams.uri, routeParams.address, routeParams.addRecipientParams]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// check if we have a suitable wallet
|
// check if we have a suitable wallet
|
||||||
@ -278,7 +271,7 @@ const SendDetails = () => {
|
|||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
setNetworkTransactionFeesIsLoading(false);
|
setNetworkTransactionFeesIsLoading(false);
|
||||||
});
|
});
|
||||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [navigation, routeParams.walletID, wallets]);
|
||||||
|
|
||||||
// change header and reset state on wallet change
|
// change header and reset state on wallet change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -288,7 +281,7 @@ const SendDetails = () => {
|
|||||||
// reset other values
|
// reset other values
|
||||||
setUtxo(null);
|
setUtxo(null);
|
||||||
setChangeAddress(null);
|
setChangeAddress(null);
|
||||||
setIsTransactionReplaceable(wallet.type === HDSegwitBech32Wallet.type && !routeParams.noRbf);
|
setIsTransactionReplaceable(wallet.type === HDSegwitBech32Wallet.type && !routeParams.noRbf ? true : undefined);
|
||||||
|
|
||||||
// update wallet UTXO
|
// update wallet UTXO
|
||||||
wallet
|
wallet
|
||||||
@ -298,7 +291,8 @@ const SendDetails = () => {
|
|||||||
setDumb(v => !v);
|
setDumb(v => !v);
|
||||||
})
|
})
|
||||||
.catch(e => console.log('fetchUtxo error', e));
|
.catch(e => console.log('fetchUtxo error', e));
|
||||||
}, [wallet]); // eslint-disable-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [wallet]);
|
||||||
|
|
||||||
// recalc fees in effect so we don't block render
|
// recalc fees in effect so we don't block render
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -381,7 +375,7 @@ const SendDetails = () => {
|
|||||||
|
|
||||||
setFeePrecalc(newFeePrecalc);
|
setFeePrecalc(newFeePrecalc);
|
||||||
setFrozenBlance(frozen);
|
setFrozenBlance(frozen);
|
||||||
}, [wallet, networkTransactionFees, utxo, addresses, feeRate, dumb]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [wallet, networkTransactionFees, utxo, addresses, feeRate, dumb, feePrecalc]);
|
||||||
|
|
||||||
// we need to re-calculate fees if user opens-closes coin control
|
// we need to re-calculate fees if user opens-closes coin control
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
@ -685,15 +679,44 @@ const SendDetails = () => {
|
|||||||
if (newWallet) {
|
if (newWallet) {
|
||||||
setWallet(newWallet);
|
setWallet(newWallet);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [routeParams.walletID, wallets]);
|
||||||
}, [routeParams.walletID]);
|
|
||||||
|
const importQrTransactionOnBarScanned = useCallback(
|
||||||
|
(ret: any) => {
|
||||||
|
navigation.getParent()?.getParent()?.dispatch(popAction);
|
||||||
|
if (!wallet) return;
|
||||||
|
if (!ret.data) ret = { data: ret };
|
||||||
|
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||||
|
presentAlert({ title: loc.errors.error, message: 'BC-UR not decoded. This should never happen' });
|
||||||
|
} else if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||||
|
// this looks like NOT base64, so maybe its transaction's hex
|
||||||
|
// we dont support it in this flow
|
||||||
|
} else {
|
||||||
|
feeModalRef.current?.dismiss();
|
||||||
|
optionsModalRef.current?.dismiss();
|
||||||
|
// psbt base64?
|
||||||
|
|
||||||
|
// we construct PSBT object and pass to next screen
|
||||||
|
// so user can do smth with it:
|
||||||
|
const psbt = bitcoin.Psbt.fromBase64(ret.data);
|
||||||
|
|
||||||
|
navigation.navigate('PsbtWithHardwareWallet', {
|
||||||
|
memo: transactionMemo,
|
||||||
|
fromWallet: wallet,
|
||||||
|
psbt,
|
||||||
|
});
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[navigation, popAction, transactionMemo, wallet],
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* same as `importTransaction`, but opens camera instead.
|
* same as `importTransaction`, but opens camera instead.
|
||||||
*
|
*
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const importQrTransaction = () => {
|
const importQrTransaction = useCallback(() => {
|
||||||
if (wallet?.type !== WatchOnlyWallet.type) {
|
if (wallet?.type !== WatchOnlyWallet.type) {
|
||||||
return presentAlert({ title: loc.errors.error, message: 'Importing transaction in non-watchonly wallet (this should never happen)' });
|
return presentAlert({ title: loc.errors.error, message: 'Importing transaction in non-watchonly wallet (this should never happen)' });
|
||||||
}
|
}
|
||||||
@ -709,34 +732,7 @@ const SendDetails = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
}, [importQrTransactionOnBarScanned, navigation, wallet?.type]);
|
||||||
|
|
||||||
const importQrTransactionOnBarScanned = (ret: any) => {
|
|
||||||
navigation.getParent()?.getParent()?.dispatch(popAction);
|
|
||||||
if (!wallet) return;
|
|
||||||
if (!ret.data) ret = { data: ret };
|
|
||||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
|
||||||
presentAlert({ title: loc.errors.error, message: 'BC-UR not decoded. This should never happen' });
|
|
||||||
} else if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
|
||||||
// this looks like NOT base64, so maybe its transaction's hex
|
|
||||||
// we dont support it in this flow
|
|
||||||
} else {
|
|
||||||
feeModalRef.current?.dismiss();
|
|
||||||
optionsModalRef.current?.dismiss();
|
|
||||||
// psbt base64?
|
|
||||||
|
|
||||||
// we construct PSBT object and pass to next screen
|
|
||||||
// so user can do smth with it:
|
|
||||||
const psbt = bitcoin.Psbt.fromBase64(ret.data);
|
|
||||||
|
|
||||||
navigation.navigate('PsbtWithHardwareWallet', {
|
|
||||||
memo: transactionMemo,
|
|
||||||
fromWallet: wallet,
|
|
||||||
psbt,
|
|
||||||
});
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
|
* watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
|
||||||
@ -746,7 +742,7 @@ const SendDetails = () => {
|
|||||||
*
|
*
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const importTransaction = async () => {
|
const importTransaction = useCallback(async () => {
|
||||||
await optionsModalRef.current?.dismiss();
|
await optionsModalRef.current?.dismiss();
|
||||||
if (wallet?.type !== WatchOnlyWallet.type) {
|
if (wallet?.type !== WatchOnlyWallet.type) {
|
||||||
return presentAlert({ title: loc.errors.error, message: 'Importing transaction in non-watchonly wallet (this should never happen)' });
|
return presentAlert({ title: loc.errors.error, message: 'Importing transaction in non-watchonly wallet (this should never happen)' });
|
||||||
@ -798,7 +794,7 @@ const SendDetails = () => {
|
|||||||
presentAlert({ title: loc.errors.error, message: loc.send.details_no_signed_tx });
|
presentAlert({ title: loc.errors.error, message: loc.send.details_no_signed_tx });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}, [navigation, optionsModalRef, transactionMemo, wallet]);
|
||||||
|
|
||||||
const askCosignThisTransaction = async () => {
|
const askCosignThisTransaction = async () => {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
@ -821,53 +817,59 @@ const SendDetails = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const _importTransactionMultisig = async (base64arg: string | false) => {
|
const _importTransactionMultisig = useCallback(
|
||||||
await optionsModalRef.current?.dismiss();
|
async (base64arg: string | false) => {
|
||||||
try {
|
await optionsModalRef.current?.dismiss();
|
||||||
const base64 = base64arg || (await fs.openSignedTransaction());
|
try {
|
||||||
if (!base64) return;
|
const base64 = base64arg || (await fs.openSignedTransaction());
|
||||||
const psbt = bitcoin.Psbt.fromBase64(base64); // if it doesnt throw - all good, its valid
|
if (!base64) return;
|
||||||
|
const psbt = bitcoin.Psbt.fromBase64(base64); // if it doesnt throw - all good, its valid
|
||||||
|
|
||||||
if ((wallet as MultisigHDWallet)?.howManySignaturesCanWeMake() > 0 && (await askCosignThisTransaction())) {
|
if ((wallet as MultisigHDWallet)?.howManySignaturesCanWeMake() > 0 && (await askCosignThisTransaction())) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await sleep(100);
|
await sleep(100);
|
||||||
(wallet as MultisigHDWallet).cosignPsbt(psbt);
|
(wallet as MultisigHDWallet).cosignPsbt(psbt);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
await sleep(100);
|
await sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wallet) {
|
||||||
|
navigation.navigate('PsbtMultisig', {
|
||||||
|
memo: transactionMemo,
|
||||||
|
psbtBase64: psbt.toBase64(),
|
||||||
|
walletID: wallet.getID(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
presentAlert({ title: loc.send.problem_with_psbt, message: error.message });
|
||||||
}
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
},
|
||||||
|
[navigation, sleep, transactionMemo, wallet],
|
||||||
|
);
|
||||||
|
|
||||||
if (wallet) {
|
const importTransactionMultisig = useCallback(() => {
|
||||||
navigation.navigate('PsbtMultisig', {
|
|
||||||
memo: transactionMemo,
|
|
||||||
psbtBase64: psbt.toBase64(),
|
|
||||||
walletID: wallet.getID(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
presentAlert({ title: loc.send.problem_with_psbt, message: error.message });
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const importTransactionMultisig = () => {
|
|
||||||
return _importTransactionMultisig(false);
|
return _importTransactionMultisig(false);
|
||||||
};
|
}, [_importTransactionMultisig]);
|
||||||
|
|
||||||
const onBarScanned = (ret: any) => {
|
const onBarScanned = useCallback(
|
||||||
navigation.getParent()?.dispatch(popAction);
|
(ret: any) => {
|
||||||
if (!ret.data) ret = { data: ret };
|
navigation.getParent()?.dispatch(popAction);
|
||||||
if (ret.data.toUpperCase().startsWith('UR')) {
|
if (!ret.data) ret = { data: ret };
|
||||||
presentAlert({ title: loc.errors.error, message: 'BC-UR not decoded. This should never happen' });
|
if (ret.data.toUpperCase().startsWith('UR')) {
|
||||||
} else if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
presentAlert({ title: loc.errors.error, message: 'BC-UR not decoded. This should never happen' });
|
||||||
// this looks like NOT base64, so maybe its transaction's hex
|
} else if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||||
// we dont support it in this flow
|
// this looks like NOT base64, so maybe its transaction's hex
|
||||||
} else {
|
// we dont support it in this flow
|
||||||
// psbt base64?
|
} else {
|
||||||
return _importTransactionMultisig(ret.data);
|
// psbt base64?
|
||||||
}
|
return _importTransactionMultisig(ret.data);
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
[_importTransactionMultisig, navigation, popAction],
|
||||||
|
);
|
||||||
|
|
||||||
const importTransactionMultisigScanQr = async () => {
|
const importTransactionMultisigScanQr = useCallback(async () => {
|
||||||
await optionsModalRef.current?.dismiss();
|
await optionsModalRef.current?.dismiss();
|
||||||
requestCameraAuthorization().then(() => {
|
requestCameraAuthorization().then(() => {
|
||||||
navigation.navigate('ScanQRCodeRoot', {
|
navigation.navigate('ScanQRCodeRoot', {
|
||||||
@ -878,20 +880,19 @@ const SendDetails = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
}, [navigation, onBarScanned]);
|
||||||
|
|
||||||
const handleAddRecipient = async () => {
|
const handleAddRecipient = useCallback(async () => {
|
||||||
console.debug('handleAddRecipient');
|
console.debug('handleAddRecipient');
|
||||||
await optionsModalRef.current?.dismiss();
|
|
||||||
setAddresses(addrs => [...addrs, { address: '', key: String(Math.random()) } as IPaymentDestinations]);
|
setAddresses(addrs => [...addrs, { address: '', key: String(Math.random()) } as IPaymentDestinations]);
|
||||||
|
|
||||||
await sleep(200); // wait for animation
|
await sleep(200); // wait for animation
|
||||||
scrollView.current?.scrollToEnd();
|
scrollView.current?.scrollToEnd();
|
||||||
if (addresses.length === 0) return;
|
if (addresses.length === 0) return;
|
||||||
scrollView.current?.flashScrollIndicators();
|
scrollView.current?.flashScrollIndicators();
|
||||||
};
|
}, [addresses.length, sleep]);
|
||||||
|
|
||||||
const handleRemoveRecipient = async () => {
|
const handleRemoveRecipient = useCallback(async () => {
|
||||||
await optionsModalRef.current?.dismiss();
|
await optionsModalRef.current?.dismiss();
|
||||||
const last = scrollIndex.current === addresses.length - 1;
|
const last = scrollIndex.current === addresses.length - 1;
|
||||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
@ -904,25 +905,24 @@ const SendDetails = () => {
|
|||||||
await sleep(200); // wait for animation
|
await sleep(200); // wait for animation
|
||||||
scrollView.current?.flashScrollIndicators();
|
scrollView.current?.flashScrollIndicators();
|
||||||
if (last && Platform.OS === 'android') scrollView.current?.scrollToEnd(); // fix white screen on android
|
if (last && Platform.OS === 'android') scrollView.current?.scrollToEnd(); // fix white screen on android
|
||||||
};
|
}, [addresses.length, sleep]);
|
||||||
|
|
||||||
const handleCoinControl = async () => {
|
const handleCoinControl = useCallback(async () => {
|
||||||
await optionsModalRef.current?.dismiss();
|
await optionsModalRef.current?.dismiss();
|
||||||
if (!wallet) return;
|
if (!wallet) return;
|
||||||
navigation.navigate('CoinControl', {
|
navigation.navigate('CoinControl', {
|
||||||
walletID: wallet?.getID(),
|
walletID: wallet?.getID(),
|
||||||
onUTXOChoose: (u: CreateTransactionUtxo[]) => setUtxo(u),
|
onUTXOChoose: (u: CreateTransactionUtxo[]) => setUtxo(u),
|
||||||
});
|
});
|
||||||
};
|
}, [navigation, wallet]);
|
||||||
|
|
||||||
const handleInsertContact = async () => {
|
const handleInsertContact = useCallback(async () => {
|
||||||
await optionsModalRef.current?.dismiss();
|
await optionsModalRef.current?.dismiss();
|
||||||
if (!wallet) return;
|
if (!wallet) return;
|
||||||
navigation.navigate('PaymentCodeList', { walletID: wallet.getID() });
|
navigation.navigate('PaymentCodeList', { walletID: wallet.getID() });
|
||||||
};
|
}, [navigation, wallet]);
|
||||||
|
|
||||||
const handlePsbtSign = async () => {
|
const handlePsbtSign = useCallback(async () => {
|
||||||
await optionsModalRef.current?.dismiss();
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await new Promise(resolve => setTimeout(resolve, 100)); // sleep for animations
|
await new Promise(resolve => setTimeout(resolve, 100)); // sleep for animations
|
||||||
|
|
||||||
@ -962,37 +962,9 @@ const SendDetails = () => {
|
|||||||
showAnimatedQr: true,
|
showAnimatedQr: true,
|
||||||
psbt,
|
psbt,
|
||||||
});
|
});
|
||||||
};
|
}, [name, navigation, wallet]);
|
||||||
|
|
||||||
// Header Right Button
|
const headerRightActions = useCallback(() => {
|
||||||
|
|
||||||
const headerRightOnPress = (id: string) => {
|
|
||||||
if (id === SendDetails.actionKeys.AddRecipient) {
|
|
||||||
handleAddRecipient();
|
|
||||||
} else if (id === SendDetails.actionKeys.RemoveRecipient) {
|
|
||||||
handleRemoveRecipient();
|
|
||||||
} else if (id === SendDetails.actionKeys.SignPSBT) {
|
|
||||||
handlePsbtSign();
|
|
||||||
} else if (id === SendDetails.actionKeys.SendMax) {
|
|
||||||
onUseAllPressed();
|
|
||||||
} else if (id === SendDetails.actionKeys.AllowRBF) {
|
|
||||||
onReplaceableFeeSwitchValueChanged(!isTransactionReplaceable);
|
|
||||||
} else if (id === SendDetails.actionKeys.ImportTransaction) {
|
|
||||||
importTransaction();
|
|
||||||
} else if (id === SendDetails.actionKeys.ImportTransactionQR) {
|
|
||||||
importQrTransaction();
|
|
||||||
} else if (id === SendDetails.actionKeys.ImportTransactionMultsig) {
|
|
||||||
importTransactionMultisig();
|
|
||||||
} else if (id === SendDetails.actionKeys.CoSignTransaction) {
|
|
||||||
importTransactionMultisigScanQr();
|
|
||||||
} else if (id === SendDetails.actionKeys.CoinControl) {
|
|
||||||
handleCoinControl();
|
|
||||||
} else if (id === SendDetails.actionKeys.InsertContact) {
|
|
||||||
handleInsertContact();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const headerRightActions = () => {
|
|
||||||
const actions: Action[] & Action[][] = [];
|
const actions: Action[] & Action[][] = [];
|
||||||
if (isEditable) {
|
if (isEditable) {
|
||||||
if (wallet?.allowBIP47() && wallet?.isBIP47Enabled()) {
|
if (wallet?.allowBIP47() && wallet?.isBIP47Enabled()) {
|
||||||
@ -1006,8 +978,8 @@ const SendDetails = () => {
|
|||||||
|
|
||||||
actions.push([{ id: SendDetails.actionKeys.SendMax, text: loc.send.details_adv_full, disabled: balance === 0 || isSendMaxUsed }]);
|
actions.push([{ id: SendDetails.actionKeys.SendMax, text: loc.send.details_adv_full, disabled: balance === 0 || isSendMaxUsed }]);
|
||||||
}
|
}
|
||||||
if (wallet?.type === HDSegwitBech32Wallet.type) {
|
if (wallet?.type === HDSegwitBech32Wallet.type && isTransactionReplaceable !== undefined) {
|
||||||
actions.push([{ id: SendDetails.actionKeys.AllowRBF, text: loc.send.details_adv_fee_bump, menuStateOn: isTransactionReplaceable }]);
|
actions.push([{ id: SendDetails.actionKeys.AllowRBF, text: loc.send.details_adv_fee_bump, menuState: !!isTransactionReplaceable }]);
|
||||||
}
|
}
|
||||||
const transactionActions = [];
|
const transactionActions = [];
|
||||||
if (wallet?.type === WatchOnlyWallet.type && wallet.isHd()) {
|
if (wallet?.type === WatchOnlyWallet.type && wallet.isHd()) {
|
||||||
@ -1059,67 +1031,9 @@ const SendDetails = () => {
|
|||||||
actions.push({ id: SendDetails.actionKeys.CoinControl, text: loc.cc.header, icon: SendDetails.actionIcons.CoinControl });
|
actions.push({ id: SendDetails.actionKeys.CoinControl, text: loc.cc.header, icon: SendDetails.actionIcons.CoinControl });
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
};
|
}, [isEditable, wallet, isTransactionReplaceable, addresses, balance]);
|
||||||
const setHeaderRightOptions = () => {
|
|
||||||
navigation.setOptions({
|
|
||||||
headerRight: Platform.select({
|
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
|
||||||
ios: () => (
|
|
||||||
<ToolTipMenu
|
|
||||||
disabled={isLoading}
|
|
||||||
isButton
|
|
||||||
isMenuPrimaryAction
|
|
||||||
onPressMenuItem={headerRightOnPress}
|
|
||||||
// @ts-ignore idk how to fix
|
|
||||||
actions={headerRightActions()}
|
|
||||||
>
|
|
||||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} style={styles.advancedOptions} />
|
|
||||||
</ToolTipMenu>
|
|
||||||
),
|
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
|
||||||
default: () => (
|
|
||||||
<TouchableOpacity
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={loc._.more}
|
|
||||||
disabled={isLoading}
|
|
||||||
style={styles.advancedOptions}
|
|
||||||
onPress={() => {
|
|
||||||
optionsModalRef.current?.present();
|
|
||||||
}}
|
|
||||||
testID="advancedOptionsMenuButton"
|
|
||||||
>
|
|
||||||
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onReplaceableFeeSwitchValueChanged = (value: boolean) => {
|
const onUseAllPressed = useCallback(async () => {
|
||||||
setIsTransactionReplaceable(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// because of https://github.com/facebook/react-native/issues/21718 we use
|
|
||||||
// onScroll for android and onMomentumScrollEnd for iOS
|
|
||||||
const handleRecipientsScrollEnds = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
||||||
if (Platform.OS === 'android') return; // for android we use handleRecipientsScroll
|
|
||||||
const contentOffset = e.nativeEvent.contentOffset;
|
|
||||||
const viewSize = e.nativeEvent.layoutMeasurement;
|
|
||||||
const index = Math.floor(contentOffset.x / viewSize.width);
|
|
||||||
scrollIndex.current = index;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRecipientsScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
||||||
if (Platform.OS === 'ios') return; // for iOS we use handleRecipientsScrollEnds
|
|
||||||
const contentOffset = e.nativeEvent.contentOffset;
|
|
||||||
const viewSize = e.nativeEvent.layoutMeasurement;
|
|
||||||
const index = Math.floor(contentOffset.x / viewSize.width);
|
|
||||||
scrollIndex.current = index;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onUseAllPressed = async () => {
|
|
||||||
await optionsModalRef.current?.dismiss();
|
await optionsModalRef.current?.dismiss();
|
||||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
|
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
|
||||||
const message = frozenBalance > 0 ? loc.send.details_adv_full_sure_frozen : loc.send.details_adv_full_sure;
|
const message = frozenBalance > 0 ? loc.send.details_adv_full_sure_frozen : loc.send.details_adv_full_sure;
|
||||||
@ -1153,6 +1067,74 @@ const SendDetails = () => {
|
|||||||
],
|
],
|
||||||
{ cancelable: false },
|
{ cancelable: false },
|
||||||
);
|
);
|
||||||
|
}, [frozenBalance]);
|
||||||
|
|
||||||
|
const HeaderRightButton = useMemo(() => {
|
||||||
|
const headerRightOnPress = (id: string) => {
|
||||||
|
if (id === SendDetails.actionKeys.AddRecipient) {
|
||||||
|
handleAddRecipient();
|
||||||
|
} else if (id === SendDetails.actionKeys.RemoveRecipient) {
|
||||||
|
handleRemoveRecipient();
|
||||||
|
} else if (id === SendDetails.actionKeys.SignPSBT) {
|
||||||
|
handlePsbtSign();
|
||||||
|
} else if (id === SendDetails.actionKeys.SendMax) {
|
||||||
|
onUseAllPressed();
|
||||||
|
} else if (id === SendDetails.actionKeys.AllowRBF) {
|
||||||
|
onReplaceableFeeSwitchValueChanged(!isTransactionReplaceable);
|
||||||
|
} else if (id === SendDetails.actionKeys.ImportTransaction) {
|
||||||
|
importTransaction();
|
||||||
|
} else if (id === SendDetails.actionKeys.ImportTransactionQR) {
|
||||||
|
importQrTransaction();
|
||||||
|
} else if (id === SendDetails.actionKeys.ImportTransactionMultsig) {
|
||||||
|
importTransactionMultisig();
|
||||||
|
} else if (id === SendDetails.actionKeys.CoSignTransaction) {
|
||||||
|
importTransactionMultisigScanQr();
|
||||||
|
} else if (id === SendDetails.actionKeys.CoinControl) {
|
||||||
|
handleCoinControl();
|
||||||
|
} else if (id === SendDetails.actionKeys.InsertContact) {
|
||||||
|
handleInsertContact();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ToolTipMenu disabled={isLoading} isButton isMenuPrimaryAction onPressMenuItem={headerRightOnPress} actions={headerRightActions()}>
|
||||||
|
<Icon size={22} name="more-horiz" type="material" color={colors.foregroundColor} style={styles.advancedOptions} />
|
||||||
|
</ToolTipMenu>
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
colors.foregroundColor,
|
||||||
|
handleAddRecipient,
|
||||||
|
handleCoinControl,
|
||||||
|
handleInsertContact,
|
||||||
|
handlePsbtSign,
|
||||||
|
handleRemoveRecipient,
|
||||||
|
headerRightActions,
|
||||||
|
importQrTransaction,
|
||||||
|
importTransaction,
|
||||||
|
importTransactionMultisig,
|
||||||
|
importTransactionMultisigScanQr,
|
||||||
|
isLoading,
|
||||||
|
isTransactionReplaceable,
|
||||||
|
onUseAllPressed,
|
||||||
|
]);
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
// because of https://github.com/facebook/react-native/issues/21718 we use
|
||||||
|
// onScroll for android and onMomentumScrollEnd for iOS
|
||||||
|
const handleRecipientsScrollEnds = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
|
if (Platform.OS === 'android') return; // for android we use handleRecipientsScroll
|
||||||
|
const contentOffset = e.nativeEvent.contentOffset;
|
||||||
|
const viewSize = e.nativeEvent.layoutMeasurement;
|
||||||
|
const index = Math.floor(contentOffset.x / viewSize.width);
|
||||||
|
scrollIndex.current = index;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRecipientsScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||||
|
if (Platform.OS === 'ios') return; // for iOS we use handleRecipientsScrollEnds
|
||||||
|
const contentOffset = e.nativeEvent.contentOffset;
|
||||||
|
const viewSize = e.nativeEvent.layoutMeasurement;
|
||||||
|
const index = Math.floor(contentOffset.x / viewSize.width);
|
||||||
|
scrollIndex.current = index;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatFee = (fee: number) => formatBalance(fee, feeUnit!, true);
|
const formatFee = (fee: number) => formatBalance(fee, feeUnit!, true);
|
||||||
@ -1194,60 +1176,6 @@ const SendDetails = () => {
|
|||||||
return totalWithFee;
|
return totalWithFee;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderOptionsModal = () => {
|
|
||||||
const isSendMaxUsed = addresses.some(element => element.amount === BitcoinUnit.MAX);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BottomModal ref={optionsModalRef} backgroundColor={colors.modal} contentContainerStyle={styles.optionsContent}>
|
|
||||||
{wallet?.allowBIP47() && wallet.isBIP47Enabled() && (
|
|
||||||
<ListItem testID="InsertContactButton" title={loc.send.details_insert_contact} onPress={handleInsertContact} />
|
|
||||||
)}
|
|
||||||
{isEditable && (
|
|
||||||
<ListItem
|
|
||||||
testID="sendMaxButton"
|
|
||||||
disabled={balance === 0 || isSendMaxUsed}
|
|
||||||
title={loc.send.details_adv_full}
|
|
||||||
onPress={onUseAllPressed}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{wallet?.type === HDSegwitBech32Wallet.type && isEditable && (
|
|
||||||
<ListItem
|
|
||||||
title={loc.send.details_adv_fee_bump}
|
|
||||||
Component={TouchableWithoutFeedback}
|
|
||||||
switch={{ value: isTransactionReplaceable, onValueChange: onReplaceableFeeSwitchValueChanged }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{wallet?.type === WatchOnlyWallet.type && wallet.isHd() && (
|
|
||||||
<ListItem title={loc.send.details_adv_import} onPress={importTransaction} />
|
|
||||||
)}
|
|
||||||
{wallet?.type === WatchOnlyWallet.type && wallet.isHd() && (
|
|
||||||
<ListItem testID="ImportQrTransactionButton" title={loc.send.details_adv_import_qr} onPress={importQrTransaction} />
|
|
||||||
)}
|
|
||||||
{wallet?.type === MultisigHDWallet.type && isEditable && (
|
|
||||||
<ListItem title={loc.send.details_adv_import} onPress={importTransactionMultisig} />
|
|
||||||
)}
|
|
||||||
{wallet?.type === MultisigHDWallet.type && wallet.howManySignaturesCanWeMake() > 0 && isEditable && (
|
|
||||||
<ListItem title={loc.multisig.co_sign_transaction} onPress={importTransactionMultisigScanQr} />
|
|
||||||
)}
|
|
||||||
{isEditable && (
|
|
||||||
<>
|
|
||||||
<ListItem testID="AddRecipient" title={loc.send.details_add_rec_add} onPress={handleAddRecipient} />
|
|
||||||
<ListItem
|
|
||||||
testID="RemoveRecipient"
|
|
||||||
title={loc.send.details_add_rec_rem}
|
|
||||||
disabled={addresses.length < 2}
|
|
||||||
onPress={handleRemoveRecipient}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<ListItem testID="CoinControl" title={loc.cc.header} onPress={handleCoinControl} />
|
|
||||||
{(wallet as MultisigHDWallet)?.allowCosignPsbt() && isEditable && (
|
|
||||||
<ListItem testID="PsbtSign" title={loc.send.psbt_sign} onPress={handlePsbtSign} />
|
|
||||||
)}
|
|
||||||
</BottomModal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderCreateButton = () => {
|
const renderCreateButton = () => {
|
||||||
const totalWithFee = calculateTotalAmount();
|
const totalWithFee = calculateTotalAmount();
|
||||||
const isDisabled = totalWithFee === 0 || totalWithFee > balance || balance === 0 || isLoading || addresses.length === 0;
|
const isDisabled = totalWithFee === 0 || totalWithFee > balance || balance === 0 || isLoading || addresses.length === 0;
|
||||||
@ -1406,6 +1334,21 @@ const SendDetails = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setHeaderRightOptions = useCallback(() => {
|
||||||
|
// Header Right Button
|
||||||
|
|
||||||
|
navigation.setOptions({
|
||||||
|
headerRight: () => HeaderRightButton,
|
||||||
|
});
|
||||||
|
}, [HeaderRightButton, navigation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('send/details - useEffect');
|
||||||
|
if (wallet) {
|
||||||
|
setHeaderRightOptions();
|
||||||
|
}
|
||||||
|
}, [colors, wallet, isTransactionReplaceable, balance, addresses, isEditable, isLoading, setHeaderRightOptions]);
|
||||||
|
|
||||||
if (isLoading || !wallet) {
|
if (isLoading || !wallet) {
|
||||||
return (
|
return (
|
||||||
<View style={[styles.loading, stylesHook.loading]}>
|
<View style={[styles.loading, stylesHook.loading]}>
|
||||||
@ -1475,7 +1418,6 @@ const SendDetails = () => {
|
|||||||
setCustomFee={setCustomFee}
|
setCustomFee={setCustomFee}
|
||||||
setFeePrecalc={setFeePrecalc}
|
setFeePrecalc={setFeePrecalc}
|
||||||
/>
|
/>
|
||||||
{renderOptionsModal()}
|
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</View>
|
</View>
|
||||||
<BlueDismissKeyboardInputAccessory />
|
<BlueDismissKeyboardInputAccessory />
|
||||||
@ -1538,11 +1480,6 @@ const styles = StyleSheet.create({
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
right: 8,
|
right: 8,
|
||||||
},
|
},
|
||||||
|
|
||||||
optionsContent: {
|
|
||||||
padding: 22,
|
|
||||||
},
|
|
||||||
|
|
||||||
createButton: {
|
createButton: {
|
||||||
marginVertical: 16,
|
marginVertical: 16,
|
||||||
marginHorizontal: 16,
|
marginHorizontal: 16,
|
||||||
|
Loading…
Reference in New Issue
Block a user