mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-13 19:16:52 +01:00
Merge branch 'master' into renovate/react-native-menu-menu-digest
This commit is contained in:
commit
670ad6a833
6 changed files with 254 additions and 243 deletions
|
@ -7,24 +7,18 @@ import { GROUP_IO_BLUEWALLET } from '../blue_modules/currency';
|
|||
import { BlueApp } from '../class';
|
||||
import { HandOffComponentProps } from './types';
|
||||
|
||||
const HandOffComponent: React.FC<HandOffComponentProps> = React.memo(
|
||||
props => {
|
||||
const { isHandOffUseEnabled } = useSettings();
|
||||
const HandOffComponent: React.FC<HandOffComponentProps> = props => {
|
||||
const { isHandOffUseEnabled } = useSettings();
|
||||
if (!props || !props.type || !props.userInfo || Object.keys(props.userInfo).length === 0) {
|
||||
console.debug('HandOffComponent: Missing required type or userInfo data');
|
||||
return null;
|
||||
}
|
||||
const userInfo = JSON.stringify(props.userInfo);
|
||||
console.debug(`HandOffComponent is rendering. Type: ${props.type}, UserInfo: ${userInfo}...`);
|
||||
return isHandOffUseEnabled ? <Handoff {...props} /> : null;
|
||||
};
|
||||
|
||||
if (!props || !props.type || !props.userInfo || Object.keys(props.userInfo).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return isHandOffUseEnabled ? <Handoff {...props} /> : null;
|
||||
},
|
||||
(prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.type === nextProps.type &&
|
||||
JSON.stringify(prevProps.userInfo) === JSON.stringify(nextProps.userInfo) &&
|
||||
prevProps.title === nextProps.title
|
||||
);
|
||||
},
|
||||
);
|
||||
const MemoizedHandOffComponent = React.memo(HandOffComponent);
|
||||
|
||||
export const setIsHandOffUseEnabled = async (value: boolean) => {
|
||||
try {
|
||||
|
@ -50,4 +44,4 @@ export const getIsHandOffUseEnabled = async (): Promise<boolean> => {
|
|||
}
|
||||
};
|
||||
|
||||
export default HandOffComponent;
|
||||
export default MemoizedHandOffComponent;
|
||||
|
|
|
@ -40,9 +40,7 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
|||
borderBottomColor: colors.lightBorder,
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
list: {
|
||||
color: colors.buttonTextColor,
|
||||
},
|
||||
|
||||
index: {
|
||||
color: colors.alternativeTextColor,
|
||||
},
|
||||
|
@ -156,19 +154,23 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
|||
isButton
|
||||
>
|
||||
<ListItem key={item.key} containerStyle={stylesHook.container}>
|
||||
<ListItem.Content style={stylesHook.list}>
|
||||
<ListItem.Title style={stylesHook.list} numberOfLines={1} ellipsizeMode="middle">
|
||||
<Text style={[styles.index, stylesHook.index]}>{item.index + 1}</Text>{' '}
|
||||
<Text style={[stylesHook.address, styles.address]}>{item.address}</Text>
|
||||
</ListItem.Title>
|
||||
<View style={styles.subtitle}>
|
||||
<Text style={[stylesHook.list, styles.balance, stylesHook.balance]}>{balance}</Text>
|
||||
<ListItem.Content>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.leftSection}>
|
||||
<Text style={[styles.index, stylesHook.index]}>{item.index}</Text>
|
||||
</View>
|
||||
<View style={styles.middleSection}>
|
||||
<Text style={[stylesHook.address, styles.address]} numberOfLines={1} ellipsizeMode="middle">
|
||||
{item.address}
|
||||
</Text>
|
||||
<Text style={[stylesHook.balance, styles.balance]}>{balance}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ListItem.Content>
|
||||
<View>
|
||||
<View style={styles.rightContainer}>
|
||||
<AddressTypeBadge isInternal={item.isInternal} hasTransactions={hasTransactions} />
|
||||
<Text style={[stylesHook.list, styles.balance, stylesHook.balance]}>
|
||||
{loc.addresses.transactions}: {item.transactions}
|
||||
<Text style={[stylesHook.balance, styles.balance]}>
|
||||
{loc.addresses.transactions}: {item.transactions ?? 0}
|
||||
</Text>
|
||||
</View>
|
||||
</ListItem>
|
||||
|
@ -179,20 +181,27 @@ const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }: Ad
|
|||
const styles = StyleSheet.create({
|
||||
address: {
|
||||
fontWeight: 'bold',
|
||||
marginHorizontal: 40,
|
||||
marginHorizontal: 4,
|
||||
},
|
||||
index: {
|
||||
fontSize: 15,
|
||||
},
|
||||
balance: {
|
||||
marginTop: 8,
|
||||
marginLeft: 14,
|
||||
marginTop: 4,
|
||||
},
|
||||
subtitle: {
|
||||
flex: 1,
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
},
|
||||
leftSection: {
|
||||
marginRight: 8,
|
||||
},
|
||||
middleSection: {
|
||||
flex: 1,
|
||||
},
|
||||
rightContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -136,6 +136,9 @@
|
|||
"details_add_recc_rem_all_alert_description": "Are you sure you want to remove all recipients?",
|
||||
"details_add_rec_rem_all": "Remove All Recipients",
|
||||
"details_recipients_title": "Recipients",
|
||||
"details_recipient_title": "Recipient #{number} of #{total}",
|
||||
"please_complete_recipient_title": "Incomplete Recipient",
|
||||
"please_complete_recipient_details": "Please complete the details of recipient #{number} before adding a new recipient.",
|
||||
"details_address": "Address",
|
||||
"details_address_field_is_not_valid": "The address is not valid.",
|
||||
"details_adv_fee_bump": "Allow Fee Bump",
|
||||
|
|
|
@ -31,37 +31,6 @@ import TipBox from '../../components/TipBox';
|
|||
|
||||
const segmentControlValues = [loc.wallets.details_address, loc.bip47.payment_code];
|
||||
|
||||
const TabContent = React.memo(
|
||||
({ currentTab, bip21encoded, wallet, address, showAddress, isCustom, handleShareButtonPressed, renderReceiveDetails }) => {
|
||||
const qrValue = useMemo(
|
||||
() => (currentTab === segmentControlValues[0] ? bip21encoded : wallet?.getBIP47PaymentCode()),
|
||||
[currentTab, bip21encoded, wallet],
|
||||
);
|
||||
|
||||
if (currentTab === segmentControlValues[0]) {
|
||||
return <View style={styles.container}>{address && renderReceiveDetails()}</View>;
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{!qrValue && <Text>{loc.bip47.not_found}</Text>}
|
||||
{qrValue && (
|
||||
<>
|
||||
<TipBox description={loc.receive.bip47_explanation} containerStyle={styles.tip} />
|
||||
<QRCodeComponent value={qrValue} />
|
||||
<CopyTextToClipboard text={qrValue} truncated={false} />
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const MemoizedHandoffComponent = React.memo(
|
||||
({ address }) => <HandOffComponent title={loc.send.details_address} type={HandOffActivityType.ReceiveOnchain} userInfo={{ address }} />,
|
||||
(prevProps, nextProps) => prevProps.address === nextProps.address,
|
||||
);
|
||||
|
||||
const ReceiveDetails = () => {
|
||||
const { walletID, address } = useRoute().params;
|
||||
const { wallets, saveToDisk, sleep, fetchAndSaveWalletTransactions } = useStorage();
|
||||
|
@ -459,37 +428,25 @@ const ReceiveDetails = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleTabChange = useCallback(index => {
|
||||
setIsLoading(true);
|
||||
// Use requestAnimationFrame to prevent UI blocking (better than InteractionManager)
|
||||
requestAnimationFrame(() => {
|
||||
setCurrentTab(segmentControlValues[index]);
|
||||
// Add a small delay to allow the UI to update (sadly needed hack)
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 100);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const renderTabContent = () => {
|
||||
if (isLoading) {
|
||||
return <BlueLoading />;
|
||||
}
|
||||
const qrValue = currentTab === segmentControlValues[0] ? bip21encoded : wallet.getBIP47PaymentCode();
|
||||
|
||||
return (
|
||||
<TabContent
|
||||
currentTab={currentTab}
|
||||
bip21encoded={bip21encoded}
|
||||
wallet={wallet}
|
||||
address={address}
|
||||
showAddress={showAddress}
|
||||
isCustom={isCustom}
|
||||
handleShareButtonPressed={handleShareButtonPressed}
|
||||
renderReceiveDetails={renderReceiveDetails}
|
||||
/>
|
||||
);
|
||||
if (currentTab === segmentControlValues[0]) {
|
||||
return <View style={styles.container}>{address && renderReceiveDetails()}</View>;
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{!qrValue && <Text>{loc.bip47.not_found}</Text>}
|
||||
{qrValue && (
|
||||
<>
|
||||
<TipBox description={loc.receive.bip47_explanation} containerStyle={styles.tip} />
|
||||
<QRCodeComponent value={qrValue} />
|
||||
<CopyTextToClipboard text={qrValue} truncated={false} />
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -504,12 +461,16 @@ const ReceiveDetails = () => {
|
|||
<SegmentedControl
|
||||
values={segmentControlValues}
|
||||
selectedIndex={segmentControlValues.findIndex(tab => tab === currentTab)}
|
||||
onChange={handleTabChange}
|
||||
onChange={index => {
|
||||
setCurrentTab(segmentControlValues[index]);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
{showAddress && renderTabContent()}
|
||||
{address !== undefined && showAddress && <MemoizedHandoffComponent address={address} />}
|
||||
{address !== undefined && showAddress && (
|
||||
<HandOffComponent title={loc.send.details_address} type={HandOffActivityType.ReceiveOnchain} userInfo={{ address }} />
|
||||
)}
|
||||
{showConfirmedBalance ? renderConfirmedBalance() : null}
|
||||
{showPendingBalance ? renderPendingBalance() : null}
|
||||
{!showAddress && !showPendingBalance && !showConfirmedBalance ? <BlueLoading /> : null}
|
||||
|
|
|
@ -136,7 +136,6 @@ const SendDetails = () => {
|
|||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [colors, wallet, isTransactionReplaceable, balance, addresses, isEditable, isLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
// decode route params
|
||||
const currentAddress = addresses[scrollIndex.current];
|
||||
|
@ -169,6 +168,7 @@ const SendDetails = () => {
|
|||
setParams({ payjoinUrl: pjUrl, amountUnit: BitcoinUnit.BTC });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ title: loc.errors.error, message: loc.send.details_error_decode });
|
||||
}
|
||||
} else if (routeParams.address) {
|
||||
|
@ -208,11 +208,11 @@ const SendDetails = () => {
|
|||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [routeParams.uri, routeParams.address, routeParams.addRecipientParams]);
|
||||
|
||||
useEffect(() => {
|
||||
// check if we have a suitable wallet
|
||||
const suitable = wallets.filter(w => w.chain === Chain.ONCHAIN && w.allowSend());
|
||||
if (suitable.length === 0) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ title: loc.errors.error, message: loc.send.details_wallet_before_tx });
|
||||
navigation.goBack();
|
||||
return;
|
||||
|
@ -245,7 +245,6 @@ const SendDetails = () => {
|
|||
})
|
||||
.catch(e => console.log('loading recommendedFees error', e))
|
||||
.finally(() => {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setNetworkTransactionFeesIsLoading(false);
|
||||
});
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
@ -353,7 +352,6 @@ const SendDetails = () => {
|
|||
setFeePrecalc(newFeePrecalc);
|
||||
setParams({ frozenBalance: frozen });
|
||||
}, [wallet, networkTransactionFees, utxos, addresses, feeRate, dumb]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// we need to re-calculate fees if user opens-closes coin control
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
|
@ -408,6 +406,7 @@ const SendDetails = () => {
|
|||
if (!data.replace) {
|
||||
// user probably scanned PSBT and got an object instead of string..?
|
||||
setIsLoading(false);
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
return presentAlert({ title: loc.errors.error, message: loc.send.details_address_field_is_not_valid });
|
||||
}
|
||||
|
||||
|
@ -524,9 +523,16 @@ const SendDetails = () => {
|
|||
}
|
||||
|
||||
if (error) {
|
||||
scrollView.current?.scrollToIndex({ index });
|
||||
// Scroll to the recipient that caused the error with animation
|
||||
scrollView.current?.scrollToIndex({ index, animated: true });
|
||||
setIsLoading(false);
|
||||
presentAlert({ title: loc.errors.error, message: error });
|
||||
presentAlert({
|
||||
title:
|
||||
addresses.length > 1
|
||||
? loc.formatString(loc.send.details_recipient_title, { number: index + 1, total: addresses.length })
|
||||
: undefined,
|
||||
message: error,
|
||||
});
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
return;
|
||||
}
|
||||
|
@ -540,6 +546,11 @@ const SendDetails = () => {
|
|||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
}
|
||||
};
|
||||
const navigateToQRCodeScanner = useCallback(() => {
|
||||
navigation.navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
const createPsbtTransaction = async () => {
|
||||
if (!wallet) return;
|
||||
|
@ -647,8 +658,7 @@ const SendDetails = () => {
|
|||
if (newWallet) {
|
||||
setWallet(newWallet);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [routeParams.walletID]);
|
||||
}, [routeParams.walletID, wallets]);
|
||||
|
||||
const setTransactionMemo = (memo: string) => {
|
||||
setParams({ transactionMemo: memo });
|
||||
|
@ -659,13 +669,13 @@ const SendDetails = () => {
|
|||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const importQrTransaction = async () => {
|
||||
const importQrTransaction = useCallback(async () => {
|
||||
if (wallet?.type !== WatchOnlyWallet.type) {
|
||||
return presentAlert({ title: loc.errors.error, message: 'Importing transaction in non-watchonly wallet (this should never happen)' });
|
||||
}
|
||||
|
||||
navigateToQRCodeScanner();
|
||||
};
|
||||
}, [navigateToQRCodeScanner, wallet?.type]);
|
||||
|
||||
const importQrTransactionOnBarScanned = useCallback(
|
||||
(ret: any) => {
|
||||
|
@ -703,7 +713,7 @@ const SendDetails = () => {
|
|||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const importTransaction = async () => {
|
||||
const importTransaction = useCallback(async () => {
|
||||
if (wallet?.type !== WatchOnlyWallet.type) {
|
||||
return presentAlert({ title: loc.errors.error, message: 'Importing transaction in non-watchonly wallet (this should never happen)' });
|
||||
}
|
||||
|
@ -748,13 +758,15 @@ const SendDetails = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ title: loc.errors.error, message: loc.send.details_unrecognized_file_format });
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ title: loc.errors.error, message: loc.send.details_no_signed_tx });
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [navigation, setIsLoading, transactionMemo, wallet]);
|
||||
|
||||
const askCosignThisTransaction = async () => {
|
||||
return new Promise(resolve => {
|
||||
|
@ -800,6 +812,7 @@ const SendDetails = () => {
|
|||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
|
||||
presentAlert({ title: loc.send.problem_with_psbt, message: error.message });
|
||||
}
|
||||
setIsLoading(false);
|
||||
|
@ -807,9 +820,9 @@ const SendDetails = () => {
|
|||
[navigation, sleep, transactionMemo, wallet],
|
||||
);
|
||||
|
||||
const importTransactionMultisig = () => {
|
||||
const importTransactionMultisig = useCallback(() => {
|
||||
return _importTransactionMultisig(false);
|
||||
};
|
||||
}, [_importTransactionMultisig]);
|
||||
|
||||
const onBarScanned = useCallback(
|
||||
(ret: any) => {
|
||||
|
@ -903,24 +916,29 @@ const SendDetails = () => {
|
|||
handlePsbtSign,
|
||||
]);
|
||||
|
||||
const navigateToQRCodeScanner = () => {
|
||||
navigation.navigate('ScanQRCode', {
|
||||
showFileImportButton: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddRecipient = () => {
|
||||
const handleAddRecipient = useCallback(() => {
|
||||
// Check if any recipient is incomplete (missing address or amount)
|
||||
const incompleteIndex = addresses.findIndex(item => !item.address || !item.amount);
|
||||
if (incompleteIndex !== -1) {
|
||||
scrollIndex.current = incompleteIndex;
|
||||
scrollView.current?.scrollToIndex({ index: incompleteIndex, animated: true });
|
||||
presentAlert({
|
||||
title: loc.send.please_complete_recipient_title,
|
||||
message: loc.formatString(loc.send.please_complete_recipient_details, { number: incompleteIndex + 1 }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Add new recipient as usual if all recipients are complete
|
||||
setAddresses(prevAddresses => [...prevAddresses, { address: '', key: String(Math.random()) } as IPaymentDestinations]);
|
||||
|
||||
// Wait for the state to update before scrolling
|
||||
setTimeout(() => {
|
||||
scrollIndex.current = addresses.length; // New index is at the end of the list
|
||||
scrollIndex.current = addresses.length; // New index at the end
|
||||
scrollView.current?.scrollToIndex({
|
||||
index: scrollIndex.current,
|
||||
animated: true,
|
||||
});
|
||||
}, 0);
|
||||
};
|
||||
}, [addresses]);
|
||||
|
||||
const onRemoveAllRecipientsConfirmed = useCallback(() => {
|
||||
setAddresses([{ address: '', key: String(Math.random()) } as IPaymentDestinations]);
|
||||
|
@ -940,7 +958,7 @@ const SendDetails = () => {
|
|||
]);
|
||||
}, [onRemoveAllRecipientsConfirmed]);
|
||||
|
||||
const handleRemoveRecipient = () => {
|
||||
const handleRemoveRecipient = useCallback(() => {
|
||||
if (addresses.length > 1) {
|
||||
const newAddresses = [...addresses];
|
||||
newAddresses.splice(scrollIndex.current, 1);
|
||||
|
@ -961,56 +979,107 @@ const SendDetails = () => {
|
|||
// Update the scroll index reference
|
||||
scrollIndex.current = newIndex;
|
||||
}
|
||||
};
|
||||
}, [addresses]);
|
||||
|
||||
const handleCoinControl = async () => {
|
||||
const handleCoinControl = useCallback(() => {
|
||||
if (!wallet) return;
|
||||
navigation.navigate('CoinControl', {
|
||||
walletID: wallet?.getID(),
|
||||
});
|
||||
};
|
||||
}, [navigation, wallet]);
|
||||
|
||||
const handleInsertContact = async () => {
|
||||
const handleInsertContact = useCallback(() => {
|
||||
if (!wallet) return;
|
||||
navigation.navigate('PaymentCodeList', { walletID: wallet.getID() });
|
||||
};
|
||||
}, [navigation, wallet]);
|
||||
|
||||
const onReplaceableFeeSwitchValueChanged = useCallback(
|
||||
(value: boolean) => {
|
||||
setParams({ isTransactionReplaceable: value });
|
||||
},
|
||||
[setParams],
|
||||
);
|
||||
|
||||
const onUseAllPressed = useCallback(() => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
|
||||
const message = frozenBalance > 0 ? loc.send.details_adv_full_sure_frozen : loc.send.details_adv_full_sure;
|
||||
|
||||
const anchor = findNodeHandle(scrollView.current);
|
||||
const options = {
|
||||
title: loc.send.details_adv_full,
|
||||
message,
|
||||
options: [loc._.cancel, loc._.ok],
|
||||
cancelButtonIndex: 0,
|
||||
anchor: anchor ?? undefined,
|
||||
};
|
||||
|
||||
ActionSheet.showActionSheetWithOptions(options, buttonIndex => {
|
||||
if (buttonIndex === 1) {
|
||||
Keyboard.dismiss();
|
||||
setAddresses(addrs => {
|
||||
addrs[scrollIndex.current].amount = BitcoinUnit.MAX;
|
||||
addrs[scrollIndex.current].amountSats = BitcoinUnit.MAX;
|
||||
return [...addrs];
|
||||
});
|
||||
setAddresses(addrs => {
|
||||
addrs[scrollIndex.current].unit = BitcoinUnit.BTC;
|
||||
return [...addrs];
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [frozenBalance]);
|
||||
// Header Right Button
|
||||
|
||||
const headerRightOnPress = (id: string) => {
|
||||
if (id === CommonToolTipActions.AddRecipient.id) {
|
||||
handleAddRecipient();
|
||||
} else if (id === CommonToolTipActions.RemoveRecipient.id) {
|
||||
handleRemoveRecipient();
|
||||
} else if (id === CommonToolTipActions.SignPSBT.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.SignPSBT;
|
||||
navigateToQRCodeScanner();
|
||||
} else if (id === CommonToolTipActions.SendMax.id) {
|
||||
onUseAllPressed();
|
||||
} else if (id === CommonToolTipActions.AllowRBF.id) {
|
||||
onReplaceableFeeSwitchValueChanged(!isTransactionReplaceable);
|
||||
} else if (id === CommonToolTipActions.ImportTransaction.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.ImportTransaction;
|
||||
importTransaction();
|
||||
} else if (id === CommonToolTipActions.ImportTransactionQR.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.ImportTransactionQR;
|
||||
importQrTransaction();
|
||||
} else if (id === CommonToolTipActions.ImportTransactionMultsig.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.ImportTransactionMultsig;
|
||||
importTransactionMultisig();
|
||||
} else if (id === CommonToolTipActions.CoSignTransaction.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.CoSignTransaction;
|
||||
navigateToQRCodeScanner();
|
||||
} else if (id === CommonToolTipActions.CoinControl.id) {
|
||||
handleCoinControl();
|
||||
} else if (id === CommonToolTipActions.InsertContact.id) {
|
||||
handleInsertContact();
|
||||
} else if (id === CommonToolTipActions.RemoveAllRecipients.id) {
|
||||
handleRemoveAllRecipients();
|
||||
}
|
||||
};
|
||||
const headerRightOnPress = useCallback(
|
||||
(id: string) => {
|
||||
if (id === CommonToolTipActions.AddRecipient.id) {
|
||||
handleAddRecipient();
|
||||
} else if (id === CommonToolTipActions.RemoveRecipient.id) {
|
||||
handleRemoveRecipient();
|
||||
} else if (id === CommonToolTipActions.SignPSBT.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.SignPSBT;
|
||||
navigateToQRCodeScanner();
|
||||
} else if (id === CommonToolTipActions.SendMax.id) {
|
||||
onUseAllPressed();
|
||||
} else if (id === CommonToolTipActions.AllowRBF.id) {
|
||||
onReplaceableFeeSwitchValueChanged(!isTransactionReplaceable);
|
||||
} else if (id === CommonToolTipActions.ImportTransaction.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.ImportTransaction;
|
||||
importTransaction();
|
||||
} else if (id === CommonToolTipActions.ImportTransactionQR.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.ImportTransactionQR;
|
||||
importQrTransaction();
|
||||
} else if (id === CommonToolTipActions.ImportTransactionMultsig.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.ImportTransactionMultsig;
|
||||
importTransactionMultisig();
|
||||
} else if (id === CommonToolTipActions.CoSignTransaction.id) {
|
||||
selectedDataProcessor.current = CommonToolTipActions.CoSignTransaction;
|
||||
navigateToQRCodeScanner();
|
||||
} else if (id === CommonToolTipActions.CoinControl.id) {
|
||||
handleCoinControl();
|
||||
} else if (id === CommonToolTipActions.InsertContact.id) {
|
||||
handleInsertContact();
|
||||
} else if (id === CommonToolTipActions.RemoveAllRecipients.id) {
|
||||
handleRemoveAllRecipients();
|
||||
}
|
||||
},
|
||||
[
|
||||
handleAddRecipient,
|
||||
handleRemoveRecipient,
|
||||
navigateToQRCodeScanner,
|
||||
onUseAllPressed,
|
||||
onReplaceableFeeSwitchValueChanged,
|
||||
isTransactionReplaceable,
|
||||
importTransaction,
|
||||
importQrTransaction,
|
||||
importTransactionMultisig,
|
||||
handleCoinControl,
|
||||
handleInsertContact,
|
||||
handleRemoveAllRecipients,
|
||||
],
|
||||
);
|
||||
|
||||
const headerRightActions = () => {
|
||||
const headerRightActions = useCallback(() => {
|
||||
if (!wallet) return [];
|
||||
|
||||
const walletActions: Action[][] = [];
|
||||
|
@ -1081,19 +1150,19 @@ const SendDetails = () => {
|
|||
walletActions.push(specificWalletActions);
|
||||
|
||||
return walletActions;
|
||||
};
|
||||
}, [addresses, isEditable, wallet, isTransactionReplaceable]);
|
||||
|
||||
const HeaderRight = useCallback(
|
||||
() => <HeaderMenuButton disabled={isLoading} onPressMenuItem={headerRightOnPress} actions={headerRightActions()} />,
|
||||
[headerRightOnPress, isLoading, headerRightActions],
|
||||
);
|
||||
|
||||
const setHeaderRightOptions = () => {
|
||||
navigation.setOptions({
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
headerRight: () => <HeaderMenuButton disabled={isLoading} onPressMenuItem={headerRightOnPress} actions={headerRightActions()} />,
|
||||
headerRight: HeaderRight,
|
||||
});
|
||||
};
|
||||
|
||||
const onReplaceableFeeSwitchValueChanged = (value: boolean) => {
|
||||
setParams({ isTransactionReplaceable: value });
|
||||
};
|
||||
|
||||
const handleRecipientsScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
const contentOffset = e.nativeEvent.contentOffset;
|
||||
const viewSize = e.nativeEvent.layoutMeasurement;
|
||||
|
@ -1101,35 +1170,6 @@ const SendDetails = () => {
|
|||
scrollIndex.current = index;
|
||||
};
|
||||
|
||||
const onUseAllPressed = () => {
|
||||
triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning);
|
||||
const message = frozenBalance > 0 ? loc.send.details_adv_full_sure_frozen : loc.send.details_adv_full_sure;
|
||||
|
||||
const anchor = findNodeHandle(scrollView.current);
|
||||
const options = {
|
||||
title: loc.send.details_adv_full,
|
||||
message,
|
||||
options: [loc._.cancel, loc._.ok],
|
||||
cancelButtonIndex: 0,
|
||||
anchor: anchor ?? undefined,
|
||||
};
|
||||
|
||||
ActionSheet.showActionSheetWithOptions(options, buttonIndex => {
|
||||
if (buttonIndex === 1) {
|
||||
Keyboard.dismiss();
|
||||
setAddresses(addrs => {
|
||||
addrs[scrollIndex.current].amount = BitcoinUnit.MAX;
|
||||
addrs[scrollIndex.current].amountSats = BitcoinUnit.MAX;
|
||||
return [...addrs];
|
||||
});
|
||||
setAddresses(addrs => {
|
||||
addrs[scrollIndex.current].unit = BitcoinUnit.BTC;
|
||||
return [...addrs];
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const formatFee = (fee: number) => formatBalance(fee, feeUnit!, true);
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
|
|
|
@ -139,6 +139,25 @@ const WalletAddresses: React.FC = () => {
|
|||
},
|
||||
});
|
||||
|
||||
const getAddresses = useMemo(() => {
|
||||
if (!walletInstance) return [];
|
||||
const newAddresses: Address[] = [];
|
||||
// @ts-ignore: idk what to do
|
||||
for (let index = 0; index <= (walletInstance?.next_free_change_address_index ?? 0); index++) {
|
||||
newAddresses.push(getAddress(walletInstance, index, true));
|
||||
}
|
||||
// @ts-ignore: idk what to do
|
||||
for (let index = 0; index < (walletInstance?.next_free_address_index ?? 0) + (walletInstance?.gap_limit ?? 0); index++) {
|
||||
newAddresses.push(getAddress(walletInstance, index, false));
|
||||
}
|
||||
return newAddresses;
|
||||
}, [walletInstance]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: SET_ADDRESSES, payload: getAddresses });
|
||||
dispatch({ type: SET_SHOW_ADDRESSES, payload: true });
|
||||
}, [getAddresses]);
|
||||
|
||||
const filteredAddresses = useMemo(
|
||||
() => addresses.filter(address => filterByAddressType(TABS.INTERNAL, address.isInternal, currentTab)).sort(sortByAddressIndex),
|
||||
[addresses, currentTab],
|
||||
|
@ -158,36 +177,20 @@ const WalletAddresses: React.FC = () => {
|
|||
});
|
||||
}, [setOptions]);
|
||||
|
||||
const getAddresses = useCallback(() => {
|
||||
const newAddresses: Address[] = [];
|
||||
// @ts-ignore: idk what to do
|
||||
for (let index = 0; index <= (walletInstance?.next_free_change_address_index ?? 0); index++) {
|
||||
const address = getAddress(walletInstance, index, true);
|
||||
newAddresses.push(address);
|
||||
}
|
||||
|
||||
// @ts-ignore: idk what to do
|
||||
for (let index = 0; index < (walletInstance?.next_free_address_index ?? 0) + (walletInstance?.gap_limit ?? 0); index++) {
|
||||
const address = getAddress(walletInstance, index, false);
|
||||
newAddresses.push(address);
|
||||
}
|
||||
dispatch({ type: SET_ADDRESSES, payload: newAddresses });
|
||||
dispatch({ type: SET_SHOW_ADDRESSES, payload: true });
|
||||
}, [walletInstance]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (!isDesktop) disallowScreenshot(true);
|
||||
getAddresses();
|
||||
return () => {
|
||||
if (!isDesktop) disallowScreenshot(false);
|
||||
};
|
||||
}, [getAddresses]),
|
||||
}, []),
|
||||
);
|
||||
|
||||
const data =
|
||||
search.length > 0 ? filteredAddresses.filter(item => item.address.toLowerCase().includes(search.toLowerCase())) : filteredAddresses;
|
||||
|
||||
const keyExtractor = useCallback((item: Address) => item.key, []);
|
||||
|
||||
const renderRow = useCallback(
|
||||
({ item }: { item: Address }) => {
|
||||
return (
|
||||
|
@ -206,31 +209,32 @@ const WalletAddresses: React.FC = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.root, stylesHook.root]}>
|
||||
<FlatList
|
||||
contentContainerStyle={stylesHook.root}
|
||||
ref={addressList}
|
||||
data={data}
|
||||
extraData={data}
|
||||
initialNumToRender={20}
|
||||
renderItem={renderRow}
|
||||
ListEmptyComponent={search.length > 0 ? null : <ActivityIndicator />}
|
||||
centerContent={!showAddresses}
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
ListHeaderComponent={
|
||||
<SegmentedControl
|
||||
values={Object.values(TABS).map(tab => loc.addresses[`type_${tab}`])}
|
||||
selectedIndex={Object.values(TABS).findIndex(tab => tab === currentTab)}
|
||||
onChange={index => {
|
||||
const tabKey = Object.keys(TABS)[index] as TabKey;
|
||||
dispatch({ type: SET_CURRENT_TAB, payload: TABS[tabKey] });
|
||||
}}
|
||||
|
||||
// style={{ marginVertical: 10 }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<FlatList
|
||||
contentContainerStyle={stylesHook.root}
|
||||
ref={addressList}
|
||||
data={data}
|
||||
extraData={data}
|
||||
style={styles.root}
|
||||
keyExtractor={keyExtractor}
|
||||
initialNumToRender={20}
|
||||
renderItem={renderRow}
|
||||
ListEmptyComponent={search.length > 0 ? null : <ActivityIndicator />}
|
||||
centerContent={!showAddresses}
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
automaticallyAdjustContentInsets
|
||||
automaticallyAdjustsScrollIndicatorInsets
|
||||
automaticallyAdjustKeyboardInsets
|
||||
ListHeaderComponent={
|
||||
<SegmentedControl
|
||||
values={Object.values(TABS).map(tab => loc.addresses[`type_${tab}`])}
|
||||
selectedIndex={Object.values(TABS).findIndex(tab => tab === currentTab)}
|
||||
onChange={index => {
|
||||
const tabKey = Object.keys(TABS)[index] as TabKey;
|
||||
dispatch({ type: SET_CURRENT_TAB, payload: TABS[tabKey] });
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue