Merge pull request #2756 from BlueWallet/limpbrains-sendmany-index

FIX: recipients index
This commit is contained in:
GLaDOS 2021-03-12 15:29:58 +00:00 committed by GitHub
commit 95e26c38d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 34 deletions

View File

@ -57,6 +57,7 @@ const SendDetails = () => {
const navigation = useNavigation(); const navigation = useNavigation();
const { name, params: routeParams } = useRoute(); const { name, params: routeParams } = useRoute();
const scrollView = useRef(); const scrollView = useRef();
const scrollIndex = useRef(0);
const { colors } = useTheme(); const { colors } = useTheme();
// state // state
@ -68,7 +69,6 @@ const SendDetails = () => {
const [isFeeSelectionModalVisible, setIsFeeSelectionModalVisible] = useState(false); const [isFeeSelectionModalVisible, setIsFeeSelectionModalVisible] = useState(false);
const [optionsVisible, setOptionsVisible] = useState(false); const [optionsVisible, setOptionsVisible] = useState(false);
const [isTransactionReplaceable, setIsTransactionReplaceable] = useState(false); const [isTransactionReplaceable, setIsTransactionReplaceable] = useState(false);
const [recipientsScrollIndex, setRecipientsScrollIndex] = useState(0);
const [addresses, setAddresses] = useState([]); const [addresses, setAddresses] = useState([]);
const [units, setUnits] = useState([]); const [units, setUnits] = useState([]);
const [memo, setMemo] = useState(''); const [memo, setMemo] = useState('');
@ -137,7 +137,7 @@ const SendDetails = () => {
if (routeParams.uri) { if (routeParams.uri) {
try { try {
const { address, amount, memo, payjoinUrl } = DeeplinkSchemaMatch.decodeBitcoinUri(routeParams.uri); const { address, amount, memo, payjoinUrl } = DeeplinkSchemaMatch.decodeBitcoinUri(routeParams.uri);
addresses.push({ address, amount, amountSats: currency.btcToSatoshi(amount) }); addresses.push({ address, amount, amountSats: currency.btcToSatoshi(amount), key: String(Math.random()) });
initialMemo = memo; initialMemo = memo;
setAddresses(addresses); setAddresses(addresses);
setMemo(initialMemo); setMemo(initialMemo);
@ -148,13 +148,13 @@ const SendDetails = () => {
Alert.alert(loc.errors.error, loc.send.details_error_decode); Alert.alert(loc.errors.error, loc.send.details_error_decode);
} }
} else if (routeParams.address) { } else if (routeParams.address) {
addresses.push({ address: routeParams.address }); addresses.push({ address: routeParams.address, key: String(Math.random()) });
if (routeParams.memo) initialMemo = routeParams.memo; if (routeParams.memo) initialMemo = routeParams.memo;
setAddresses(addresses); setAddresses(addresses);
setMemo(initialMemo); setMemo(initialMemo);
setAmountUnit(BitcoinUnit.BTC); setAmountUnit(BitcoinUnit.BTC);
} else { } else {
setAddresses([{ address: '' }]); setAddresses([{ address: '', key: String(Math.random()) }]);
} }
// we are ready! // we are ready!
@ -343,8 +343,8 @@ const SendDetails = () => {
const unitsCopy = [...units]; const unitsCopy = [...units];
const dataWithoutSchema = data.replace('bitcoin:', '').replace('BITCOIN:', ''); const dataWithoutSchema = data.replace('bitcoin:', '').replace('BITCOIN:', '');
if (wallet.isAddressValid(dataWithoutSchema)) { if (wallet.isAddressValid(dataWithoutSchema)) {
recipients[recipientsScrollIndex].address = dataWithoutSchema; recipients[scrollIndex.current].address = dataWithoutSchema;
unitsCopy[recipientsScrollIndex] = amountUnit; unitsCopy[scrollIndex.current] = amountUnit;
setAddresses(recipients); setAddresses(recipients);
setUnits(unitsCopy); setUnits(unitsCopy);
setIsLoading(false); setIsLoading(false);
@ -368,10 +368,10 @@ const SendDetails = () => {
console.log('options', options); console.log('options', options);
if (btcAddressRx.test(address) || address.startsWith('bc1') || address.startsWith('BC1')) { if (btcAddressRx.test(address) || address.startsWith('bc1') || address.startsWith('BC1')) {
unitsCopy[recipientsScrollIndex] = BitcoinUnit.BTC; // also resetting current unit to BTC unitsCopy[scrollIndex.current] = BitcoinUnit.BTC; // also resetting current unit to BTC
recipients[recipientsScrollIndex].address = address; recipients[scrollIndex.current].address = address;
recipients[recipientsScrollIndex].amount = options.amount; recipients[scrollIndex.current].amount = options.amount;
recipients[recipientsScrollIndex].amountSats = new BigNumber(options.amount).multipliedBy(100000000).toNumber(); recipients[scrollIndex.current].amountSats = new BigNumber(options.amount).multipliedBy(100000000).toNumber();
setAddresses(recipients); setAddresses(recipients);
setMemo(options.label || options.message); setMemo(options.label || options.message);
setUnits(unitsCopy); setUnits(unitsCopy);
@ -755,24 +755,27 @@ const SendDetails = () => {
}); });
}; };
const handleAddRecipient = () => { const handleAddRecipient = async () => {
addresses.push({ address: '' }); addresses.push({ address: '', key: String(Math.random()) }); // key is for the FlatList
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut, () => scrollView.current.scrollToEnd()); LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut, () => scrollView.current.scrollToEnd());
setAddresses(addresses); setAddresses([...addresses]);
setOptionsVisible(false); setOptionsVisible(false);
scrollView.current.scrollToEnd(); scrollView.current.scrollToEnd();
if (addresses.length > 1) scrollView.current.flashScrollIndicators(); if (addresses.length === 0) return;
setRecipientsScrollIndex(addresses.length - 1); await sleep(200); // wait for animation
scrollView.current.flashScrollIndicators();
}; };
const handleRemoveRecipient = () => { const handleRemoveRecipient = async () => {
addresses.splice(recipientsScrollIndex, 1); const last = scrollIndex.current === addresses.length - 1;
addresses.splice(scrollIndex.current, 1);
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setAddresses(addresses); setAddresses([...addresses]);
setOptionsVisible(false); setOptionsVisible(false);
if (addresses.length > 1) scrollView.current.flashScrollIndicators(); if (addresses.length === 0) return;
// after deletion it automatically scrolls to the last one await sleep(200); // wait for animation
setRecipientsScrollIndex(addresses.length - 1); scrollView.current.flashScrollIndicators();
if (last && Platform.OS === 'android') scrollView.current.scrollToEnd(); // fix white screen on android
}; };
const handleCoinControl = () => { const handleCoinControl = () => {
@ -832,14 +835,22 @@ const SendDetails = () => {
setIsTransactionReplaceable(value); setIsTransactionReplaceable(value);
}; };
const scrollViewCurrentIndex = () => { // because of https://github.com/facebook/react-native/issues/21718 we use
Keyboard.dismiss(); // onScroll for android and onMomentumScrollEnd for iOS
const offset = scrollView.current.contentOffset; const handleRecipientsScrollEnds = e => {
if (offset) { if (Platform.OS === 'android') return; // for android we use handleRecipientsScroll
const page = Math.round(offset.x / Dimensions.get('window').width); const contentOffset = e.nativeEvent.contentOffset;
return page; const viewSize = e.nativeEvent.layoutMeasurement;
} const index = Math.floor(contentOffset.x / viewSize.width);
return 0; scrollIndex.current = index;
};
const handleRecipientsScroll = e => {
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 = () => { const onUseAllPressed = () => {
@ -852,7 +863,7 @@ const SendDetails = () => {
text: loc._.ok, text: loc._.ok,
onPress: async () => { onPress: async () => {
Keyboard.dismiss(); Keyboard.dismiss();
const recipient = addresses[scrollViewCurrentIndex()]; const recipient = addresses[scrollIndex.current];
recipient.amount = BitcoinUnit.MAX; recipient.amount = BitcoinUnit.MAX;
recipient.amountSats = BitcoinUnit.MAX; recipient.amountSats = BitcoinUnit.MAX;
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
@ -1114,7 +1125,7 @@ const SendDetails = () => {
{isLoading ? ( {isLoading ? (
<ActivityIndicator /> <ActivityIndicator />
) : ( ) : (
<BlueButton onPress={() => createTransaction()} title={loc.send.details_next} testID="CreateTransactionButton" /> <BlueButton onPress={createTransaction} title={loc.send.details_next} testID="CreateTransactionButton" />
)} )}
</View> </View>
); );
@ -1260,13 +1271,15 @@ const SendDetails = () => {
scrollEnabled={addresses.length > 1} scrollEnabled={addresses.length > 1}
data={addresses} data={addresses}
renderItem={renderBitcoinTransactionInfoFields} renderItem={renderBitcoinTransactionInfoFields}
keyExtractor={(_item, index) => `${index}`}
ref={scrollView} ref={scrollView}
horizontal horizontal
pagingEnabled pagingEnabled
removeClippedSubviews={false} removeClippedSubviews={false}
onMomentumScrollBegin={Keyboard.dismiss} onMomentumScrollBegin={Keyboard.dismiss}
scrollIndicatorInsets={{ top: 0, left: 8, bottom: 0, right: 8 }} onMomentumScrollEnd={handleRecipientsScrollEnds}
onScroll={handleRecipientsScroll}
scrollEventThrottle={200}
scrollIndicatorInsets={styles.scrollViewIndicator}
contentContainerStyle={styles.scrollViewContent} contentContainerStyle={styles.scrollViewContent}
/> />
<View style={[styles.memo, stylesHook.memo]}> <View style={[styles.memo, stylesHook.memo]}>
@ -1338,6 +1351,12 @@ const styles = StyleSheet.create({
scrollViewContent: { scrollViewContent: {
flexDirection: 'row', flexDirection: 'row',
}, },
scrollViewIndicator: {
top: 0,
left: 8,
bottom: 0,
right: 8,
},
modalContent: { modalContent: {
padding: 22, padding: 22,
borderTopLeftRadius: 16, borderTopLeftRadius: 16,

View File

@ -596,6 +596,16 @@ describe('BlueWallet UI Tests', () => {
await element(by.id('AddressInput').withAncestor(by.id('Transaction1'))).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl'); await element(by.id('AddressInput').withAncestor(by.id('Transaction1'))).replaceText('bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
await element(by.id('BitcoinAmountInput').withAncestor(by.id('Transaction1'))).typeText('0.0002\n'); await element(by.id('BitcoinAmountInput').withAncestor(by.id('Transaction1'))).typeText('0.0002\n');
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('AddRecipient')).tap();
await yo('Transaction2'); // adding a recipient autoscrolls it to the last one
// remove last output, check if second output is shown
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('RemoveRecipient')).tap();
await yo('Transaction1');
// adding it again
await element(by.id('advancedOptionsMenuButton')).tap(); await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.id('AddRecipient')).tap(); await element(by.id('AddRecipient')).tap();
await yo('Transaction2'); // adding a recipient autoscrolls it to the last one await yo('Transaction2'); // adding a recipient autoscrolls it to the last one
@ -619,7 +629,7 @@ describe('BlueWallet UI Tests', () => {
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[0].script), 'bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7'); assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[0].script), 'bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7');
assert.strictEqual(transaction.outs[0].value, 50000); assert.strictEqual(transaction.outs[0].value, 50000);
assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[1].script), 'bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl'); assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[1].script), 'bc1q063ctu6jhe5k4v8ka99qac8rcm2tzjjnuktyrl');
assert.strictEqual(transaction.outs[1].value, 20000); assert.strictEqual(transaction.outs[1].value, 30000);
// now, testing sendMAX feature: // now, testing sendMAX feature: