From ddcf8f4fb5efe062cba8e498808e0999bac7ed2d Mon Sep 17 00:00:00 2001 From: Ivan Vershigora Date: Fri, 12 Mar 2021 17:02:49 +0300 Subject: [PATCH] REF: move recipientsScrollIndex out of State to Ref --- screen/send/details.js | 85 ++++++++++++++++++++++-------------- tests/e2e/bluewallet.spec.js | 12 ++++- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/screen/send/details.js b/screen/send/details.js index 6cf08e5d1..fa2cc8617 100644 --- a/screen/send/details.js +++ b/screen/send/details.js @@ -57,6 +57,7 @@ const SendDetails = () => { const navigation = useNavigation(); const { name, params: routeParams } = useRoute(); // FIXME refactor to use walletID const scrollView = useRef(); + const scrollIndex = useRef(0); const { colors } = useTheme(); // state @@ -68,7 +69,6 @@ const SendDetails = () => { const [isFeeSelectionModalVisible, setIsFeeSelectionModalVisible] = useState(false); const [optionsVisible, setOptionsVisible] = useState(false); const [isTransactionReplaceable, setIsTransactionReplaceable] = useState(false); - const [recipientsScrollIndex, setRecipientsScrollIndex] = useState(0); const [addresses, setAddresses] = useState([]); const [units, setUnits] = useState([]); const [memo, setMemo] = useState(''); @@ -137,7 +137,7 @@ const SendDetails = () => { if (routeParams.uri) { try { 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; setAddresses(addresses); setMemo(initialMemo); @@ -148,13 +148,13 @@ const SendDetails = () => { Alert.alert(loc.errors.error, loc.send.details_error_decode); } } else if (routeParams.address) { - addresses.push({ address: routeParams.address }); + addresses.push({ address: routeParams.address, key: String(Math.random()) }); if (routeParams.memo) initialMemo = routeParams.memo; setAddresses(addresses); setMemo(initialMemo); setAmountUnit(BitcoinUnit.BTC); } else { - setAddresses([{ address: '' }]); + setAddresses([{ address: '', key: String(Math.random()) }]); } // we are ready! @@ -343,8 +343,8 @@ const SendDetails = () => { const unitsCopy = [...units]; const dataWithoutSchema = data.replace('bitcoin:', '').replace('BITCOIN:', ''); if (wallet.isAddressValid(dataWithoutSchema)) { - recipients[recipientsScrollIndex].address = dataWithoutSchema; - unitsCopy[recipientsScrollIndex] = amountUnit; + recipients[scrollIndex.current].address = dataWithoutSchema; + unitsCopy[scrollIndex.current] = amountUnit; setAddresses(recipients); setUnits(unitsCopy); setIsLoading(false); @@ -368,10 +368,10 @@ const SendDetails = () => { console.log('options', options); if (btcAddressRx.test(address) || address.startsWith('bc1') || address.startsWith('BC1')) { - unitsCopy[recipientsScrollIndex] = BitcoinUnit.BTC; // also resetting current unit to BTC - recipients[recipientsScrollIndex].address = address; - recipients[recipientsScrollIndex].amount = options.amount; - recipients[recipientsScrollIndex].amountSats = new BigNumber(options.amount).multipliedBy(100000000).toNumber(); + unitsCopy[scrollIndex.current] = BitcoinUnit.BTC; // also resetting current unit to BTC + recipients[scrollIndex.current].address = address; + recipients[scrollIndex.current].amount = options.amount; + recipients[scrollIndex.current].amountSats = new BigNumber(options.amount).multipliedBy(100000000).toNumber(); setAddresses(recipients); setMemo(options.label || options.message); setUnits(unitsCopy); @@ -755,24 +755,27 @@ const SendDetails = () => { }); }; - const handleAddRecipient = () => { - addresses.push({ address: '' }); + const handleAddRecipient = async () => { + addresses.push({ address: '', key: String(Math.random()) }); // key is for the FlatList LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut, () => scrollView.current.scrollToEnd()); - setAddresses(addresses); + setAddresses([...addresses]); setOptionsVisible(false); scrollView.current.scrollToEnd(); - if (addresses.length > 1) scrollView.current.flashScrollIndicators(); - setRecipientsScrollIndex(addresses.length - 1); + if (addresses.length === 0) return; + await sleep(200); // wait for animation + scrollView.current.flashScrollIndicators(); }; - const handleRemoveRecipient = () => { - addresses.splice(recipientsScrollIndex, 1); + const handleRemoveRecipient = async () => { + const last = scrollIndex.current === addresses.length - 1; + addresses.splice(scrollIndex.current, 1); LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); - setAddresses(addresses); + setAddresses([...addresses]); setOptionsVisible(false); - if (addresses.length > 1) scrollView.current.flashScrollIndicators(); - // after deletion it automatically scrolls to the last one - setRecipientsScrollIndex(addresses.length - 1); + if (addresses.length === 0) return; + await sleep(200); // wait for animation + scrollView.current.flashScrollIndicators(); + if (last && Platform.OS === 'android') scrollView.current.scrollToEnd(); // fix white screen on android }; const handleCoinControl = () => { @@ -832,14 +835,22 @@ const SendDetails = () => { setIsTransactionReplaceable(value); }; - const scrollViewCurrentIndex = () => { - Keyboard.dismiss(); - const offset = scrollView.current.contentOffset; - if (offset) { - const page = Math.round(offset.x / Dimensions.get('window').width); - return page; - } - return 0; + // because of https://github.com/facebook/react-native/issues/21718 we use + // onScroll for android and onMomentumScrollEnd for iOS + const handleRecipientsScrollEnds = e => { + 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 => { + 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 = () => { @@ -852,7 +863,7 @@ const SendDetails = () => { text: loc._.ok, onPress: async () => { Keyboard.dismiss(); - const recipient = addresses[scrollViewCurrentIndex()]; + const recipient = addresses[scrollIndex.current]; recipient.amount = BitcoinUnit.MAX; recipient.amountSats = BitcoinUnit.MAX; LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); @@ -1114,7 +1125,7 @@ const SendDetails = () => { {isLoading ? ( ) : ( - createTransaction()} title={loc.send.details_next} testID="CreateTransactionButton" /> + )} ); @@ -1260,13 +1271,15 @@ const SendDetails = () => { scrollEnabled={addresses.length > 1} data={addresses} renderItem={renderBitcoinTransactionInfoFields} - keyExtractor={(_item, index) => `${index}`} ref={scrollView} horizontal pagingEnabled removeClippedSubviews={false} onMomentumScrollBegin={Keyboard.dismiss} - scrollIndicatorInsets={{ top: 0, left: 8, bottom: 0, right: 8 }} + onMomentumScrollEnd={handleRecipientsScrollEnds} + onScroll={handleRecipientsScroll} + scrollEventThrottle={200} + scrollIndicatorInsets={styles.scrollViewIndicator} contentContainerStyle={styles.scrollViewContent} /> @@ -1338,6 +1351,12 @@ const styles = StyleSheet.create({ scrollViewContent: { flexDirection: 'row', }, + scrollViewIndicator: { + top: 0, + left: 8, + bottom: 0, + right: 8, + }, modalContent: { padding: 22, borderTopLeftRadius: 16, diff --git a/tests/e2e/bluewallet.spec.js b/tests/e2e/bluewallet.spec.js index b730296c2..c4c2976d0 100644 --- a/tests/e2e/bluewallet.spec.js +++ b/tests/e2e/bluewallet.spec.js @@ -479,6 +479,16 @@ describe('BlueWallet UI Tests', () => { 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('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('AddRecipient')).tap(); await yo('Transaction2'); // adding a recipient autoscrolls it to the last one @@ -502,7 +512,7 @@ describe('BlueWallet UI Tests', () => { assert.strictEqual(bitcoin.address.fromOutputScript(transaction.outs[0].script), 'bc1qnapskphjnwzw2w3dk4anpxntunc77v6qrua0f7'); assert.strictEqual(transaction.outs[0].value, 50000); 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: