mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-03-03 20:07:11 +01:00
REF: Refactor Batch UI to use FlatList
This commit is contained in:
parent
826a0ea499
commit
b0898fe501
1 changed files with 115 additions and 131 deletions
|
@ -14,8 +14,9 @@ import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Platform,
|
Platform,
|
||||||
ScrollView,
|
|
||||||
Text,
|
Text,
|
||||||
|
LayoutAnimation,
|
||||||
|
FlatList,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Icon } from 'react-native-elements';
|
import { Icon } from 'react-native-elements';
|
||||||
import AsyncStorage from '@react-native-community/async-storage';
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
|
@ -64,7 +65,6 @@ const styles = StyleSheet.create({
|
||||||
backgroundColor: BlueCurrentTheme.colors.elevated,
|
backgroundColor: BlueCurrentTheme.colors.elevated,
|
||||||
},
|
},
|
||||||
scrollViewContent: {
|
scrollViewContent: {
|
||||||
flexWrap: 'wrap',
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
},
|
},
|
||||||
modalContent: {
|
modalContent: {
|
||||||
|
@ -212,6 +212,7 @@ const styles = StyleSheet.create({
|
||||||
export default class SendDetails extends Component {
|
export default class SendDetails extends Component {
|
||||||
static contextType = BlueStorageContext;
|
static contextType = BlueStorageContext;
|
||||||
state = { isLoading: true };
|
state = { isLoading: true };
|
||||||
|
scrollView = React.createRef();
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -254,7 +255,7 @@ export default class SendDetails extends Component {
|
||||||
feeUnit: fromWallet.getPreferredBalanceUnit(),
|
feeUnit: fromWallet.getPreferredBalanceUnit(),
|
||||||
amountUnit: fromWallet.preferredBalanceUnit, // default for whole screen
|
amountUnit: fromWallet.preferredBalanceUnit, // default for whole screen
|
||||||
renderWalletSelectionButtonHidden: false,
|
renderWalletSelectionButtonHidden: false,
|
||||||
width: Dimensions.get('window').width - 320,
|
width: Dimensions.get('window').width,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -450,15 +451,8 @@ export default class SendDetails extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error) {
|
||||||
if (index === 0) {
|
this.scrollView.current.scrollToIndex({ index });
|
||||||
this.scrollView.scrollTo();
|
this.setState({ isLoading: false });
|
||||||
} else if (index === this.state.addresses.length - 1) {
|
|
||||||
this.scrollView.scrollToEnd();
|
|
||||||
} else {
|
|
||||||
const page = Math.round(this.state.width * (this.state.addresses.length - 2));
|
|
||||||
this.scrollView.scrollTo({ x: page, y: 0, animated: true });
|
|
||||||
}
|
|
||||||
this.setState({ isLoading: false, recipientsScrollIndex: index });
|
|
||||||
alert(error);
|
alert(error);
|
||||||
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
|
||||||
return;
|
return;
|
||||||
|
@ -696,7 +690,8 @@ export default class SendDetails extends Component {
|
||||||
const feeSatoshi = new BigNumber(element.amount).multipliedBy(100000000);
|
const feeSatoshi = new BigNumber(element.amount).multipliedBy(100000000);
|
||||||
return element.address.length > 0 && feeSatoshi > 0;
|
return element.address.length > 0 && feeSatoshi > 0;
|
||||||
}) || this.state.addresses[0];
|
}) || this.state.addresses[0];
|
||||||
this.setState({ addresses: [firstTransaction], recipientsScrollIndex: 0 }, () => changeWallet());
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
|
this.setState({ addresses: [firstTransaction] }, () => changeWallet());
|
||||||
},
|
},
|
||||||
style: 'default',
|
style: 'default',
|
||||||
},
|
},
|
||||||
|
@ -718,7 +713,8 @@ export default class SendDetails extends Component {
|
||||||
return element.amount === BitcoinUnit.MAX;
|
return element.amount === BitcoinUnit.MAX;
|
||||||
}) || this.state.addresses[0];
|
}) || this.state.addresses[0];
|
||||||
firstTransaction.amount = 0;
|
firstTransaction.amount = 0;
|
||||||
this.setState({ addresses: [firstTransaction], recipientsScrollIndex: 0 }, () => changeWallet());
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
|
this.setState({ addresses: [firstTransaction] }, () => changeWallet());
|
||||||
},
|
},
|
||||||
style: 'default',
|
style: 'default',
|
||||||
},
|
},
|
||||||
|
@ -965,14 +961,15 @@ export default class SendDetails extends Component {
|
||||||
handleAddRecipient = () => {
|
handleAddRecipient = () => {
|
||||||
const { addresses } = this.state;
|
const { addresses } = this.state;
|
||||||
addresses.push(new BitcoinTransaction());
|
addresses.push(new BitcoinTransaction());
|
||||||
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut, () => this.scrollView.current.scrollToEnd());
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
addresses,
|
addresses,
|
||||||
isAdvancedTransactionOptionsVisible: false,
|
isAdvancedTransactionOptionsVisible: false,
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
this.scrollView.scrollToEnd();
|
this.scrollView.current.scrollToEnd();
|
||||||
if (this.state.addresses.length > 1) this.scrollView.flashScrollIndicators();
|
if (this.state.addresses.length > 1) this.scrollView.current.flashScrollIndicators();
|
||||||
// after adding recipient it automatically scrolls to the last one
|
// after adding recipient it automatically scrolls to the last one
|
||||||
this.setState({ recipientsScrollIndex: this.state.addresses.length - 1 });
|
this.setState({ recipientsScrollIndex: this.state.addresses.length - 1 });
|
||||||
},
|
},
|
||||||
|
@ -982,13 +979,14 @@ export default class SendDetails extends Component {
|
||||||
handleRemoveRecipient = () => {
|
handleRemoveRecipient = () => {
|
||||||
const { addresses } = this.state;
|
const { addresses } = this.state;
|
||||||
addresses.splice(this.state.recipientsScrollIndex, 1);
|
addresses.splice(this.state.recipientsScrollIndex, 1);
|
||||||
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
addresses,
|
addresses,
|
||||||
isAdvancedTransactionOptionsVisible: false,
|
isAdvancedTransactionOptionsVisible: false,
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
if (this.state.addresses.length > 1) this.scrollView.flashScrollIndicators();
|
if (this.state.addresses.length > 1) this.scrollView.current.flashScrollIndicators();
|
||||||
// after deletion it automatically scrolls to the last one
|
// after deletion it automatically scrolls to the last one
|
||||||
this.setState({ recipientsScrollIndex: this.state.addresses.length - 1 });
|
this.setState({ recipientsScrollIndex: this.state.addresses.length - 1 });
|
||||||
},
|
},
|
||||||
|
@ -1081,6 +1079,16 @@ export default class SendDetails extends Component {
|
||||||
this.setState({ isTransactionReplaceable: value });
|
this.setState({ isTransactionReplaceable: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
scrollViewCurrentIndex = () => {
|
||||||
|
Keyboard.dismiss();
|
||||||
|
const offset = this.scrollView.current.contentOffset;
|
||||||
|
if (offset) {
|
||||||
|
const page = Math.round(offset.x / Dimensions.get('window').width);
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
renderCreateButton = () => {
|
renderCreateButton = () => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.createButton}>
|
<View style={styles.createButton}>
|
||||||
|
@ -1122,110 +1130,84 @@ export default class SendDetails extends Component {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePageChange = e => {
|
renderBitcoinTransactionInfoFields = ({ item, index }) => {
|
||||||
Keyboard.dismiss();
|
return (
|
||||||
const offset = e.nativeEvent.contentOffset;
|
<View style={{ width: this.state.width }}>
|
||||||
if (offset) {
|
<BlueBitcoinAmount
|
||||||
const page = Math.round(offset.x / this.state.width);
|
isLoading={this.state.isLoading}
|
||||||
if (this.state.recipientsScrollIndex !== page) {
|
amount={item.amount ? item.amount.toString() : null}
|
||||||
this.setState({ recipientsScrollIndex: page });
|
onAmountUnitChange={unit => {
|
||||||
}
|
const units = this.state.units;
|
||||||
}
|
units[index] = unit;
|
||||||
};
|
|
||||||
|
|
||||||
scrollViewCurrentIndex = () => {
|
const addresses = this.state.addresses;
|
||||||
Keyboard.dismiss();
|
const item = addresses[index];
|
||||||
const offset = this.scrollView.contentOffset;
|
|
||||||
if (offset) {
|
|
||||||
const page = Math.round(offset.x / this.state.width);
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderBitcoinTransactionInfoFields = () => {
|
switch (unit) {
|
||||||
const rows = [];
|
case BitcoinUnit.SATS:
|
||||||
|
item.amountSats = parseInt(item.amount);
|
||||||
|
break;
|
||||||
|
case BitcoinUnit.BTC:
|
||||||
|
item.amountSats = currency.btcToSatoshi(item.amount);
|
||||||
|
break;
|
||||||
|
case BitcoinUnit.LOCAL_CURRENCY:
|
||||||
|
// also accounting for cached fiat->sat conversion to avoid rounding error
|
||||||
|
item.amountSats =
|
||||||
|
BlueBitcoinAmount.getCachedSatoshis(item.amount) || currency.btcToSatoshi(currency.fiatToBTC(item.amount));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
for (const [index, item] of this.state.addresses.entries()) {
|
addresses[index] = item;
|
||||||
rows.push(
|
this.setState({ units, addresses });
|
||||||
<View key={index} style={{ width: this.state.width }}>
|
}}
|
||||||
<BlueBitcoinAmount
|
onChangeText={text => {
|
||||||
isLoading={this.state.isLoading}
|
item.amount = text;
|
||||||
amount={item.amount ? item.amount.toString() : null}
|
switch (this.state.units[index] || this.state.amountUnit) {
|
||||||
onAmountUnitChange={unit => {
|
case BitcoinUnit.BTC:
|
||||||
const units = this.state.units;
|
item.amountSats = currency.btcToSatoshi(item.amount);
|
||||||
units[index] = unit;
|
break;
|
||||||
|
case BitcoinUnit.LOCAL_CURRENCY:
|
||||||
const addresses = this.state.addresses;
|
item.amountSats = currency.btcToSatoshi(currency.fiatToBTC(item.amount));
|
||||||
const item = addresses[index];
|
break;
|
||||||
|
default:
|
||||||
switch (unit) {
|
case BitcoinUnit.SATS:
|
||||||
case BitcoinUnit.SATS:
|
item.amountSats = parseInt(text);
|
||||||
item.amountSats = parseInt(item.amount);
|
break;
|
||||||
break;
|
}
|
||||||
case BitcoinUnit.BTC:
|
const addresses = this.state.addresses;
|
||||||
item.amountSats = currency.btcToSatoshi(item.amount);
|
addresses[index] = item;
|
||||||
break;
|
this.setState({ addresses }, this.reCalcTx);
|
||||||
case BitcoinUnit.LOCAL_CURRENCY:
|
}}
|
||||||
// also accounting for cached fiat->sat conversion to avoid rounding error
|
unit={this.state.units[index] || this.state.amountUnit}
|
||||||
item.amountSats =
|
inputAccessoryViewID={this.state.fromWallet.allowSendMax() ? BlueUseAllFundsButton.InputAccessoryViewID : null}
|
||||||
BlueBitcoinAmount.getCachedSatoshis(item.amount) || currency.btcToSatoshi(currency.fiatToBTC(item.amount));
|
/>
|
||||||
break;
|
<BlueAddressInput
|
||||||
}
|
onChangeText={async text => {
|
||||||
|
text = text.trim();
|
||||||
addresses[index] = item;
|
const transactions = this.state.addresses;
|
||||||
this.setState({ units, addresses });
|
const { address, amount, memo, payjoinUrl } = DeeplinkSchemaMatch.decodeBitcoinUri(text);
|
||||||
}}
|
item.address = address || text;
|
||||||
onChangeText={text => {
|
item.amount = amount || item.amount;
|
||||||
item.amount = text;
|
transactions[index] = item;
|
||||||
switch (this.state.units[index] || this.state.amountUnit) {
|
this.setState({
|
||||||
case BitcoinUnit.BTC:
|
addresses: transactions,
|
||||||
item.amountSats = currency.btcToSatoshi(item.amount);
|
memo: memo || this.state.memo,
|
||||||
break;
|
isLoading: false,
|
||||||
case BitcoinUnit.LOCAL_CURRENCY:
|
payjoinUrl,
|
||||||
item.amountSats = currency.btcToSatoshi(currency.fiatToBTC(item.amount));
|
});
|
||||||
break;
|
this.reCalcTx();
|
||||||
default:
|
}}
|
||||||
case BitcoinUnit.SATS:
|
onBarScanned={this.processAddressData}
|
||||||
item.amountSats = parseInt(text);
|
address={item.address}
|
||||||
break;
|
isLoading={this.state.isLoading}
|
||||||
}
|
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
|
||||||
const addresses = this.state.addresses;
|
launchedBy={this.props.route.name}
|
||||||
addresses[index] = item;
|
/>
|
||||||
this.setState({ addresses }, this.reCalcTx);
|
{this.state.addresses.length > 1 && (
|
||||||
}}
|
<BlueText style={styles.of}>{loc.formatString(loc._.of, { number: index + 1, total: this.state.addresses.length })}</BlueText>
|
||||||
unit={this.state.units[index] || this.state.amountUnit}
|
)}
|
||||||
inputAccessoryViewID={this.state.fromWallet.allowSendMax() ? BlueUseAllFundsButton.InputAccessoryViewID : null}
|
</View>
|
||||||
/>
|
);
|
||||||
<BlueAddressInput
|
|
||||||
onChangeText={async text => {
|
|
||||||
text = text.trim();
|
|
||||||
const transactions = this.state.addresses;
|
|
||||||
const { address, amount, memo, payjoinUrl } = DeeplinkSchemaMatch.decodeBitcoinUri(text);
|
|
||||||
item.address = address || text;
|
|
||||||
item.amount = amount || item.amount;
|
|
||||||
transactions[index] = item;
|
|
||||||
this.setState({
|
|
||||||
addresses: transactions,
|
|
||||||
memo: memo || this.state.memo,
|
|
||||||
isLoading: false,
|
|
||||||
payjoinUrl,
|
|
||||||
});
|
|
||||||
this.reCalcTx();
|
|
||||||
}}
|
|
||||||
onBarScanned={this.processAddressData}
|
|
||||||
address={item.address}
|
|
||||||
isLoading={this.state.isLoading}
|
|
||||||
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
|
|
||||||
launchedBy={this.props.route.name}
|
|
||||||
/>
|
|
||||||
{this.state.addresses.length > 1 && (
|
|
||||||
<BlueText style={styles.of}>{loc.formatString(loc._.of, { number: index + 1, total: this.state.addresses.length })}</BlueText>
|
|
||||||
)}
|
|
||||||
</View>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return rows;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onUseAllPressed = () => {
|
onUseAllPressed = () => {
|
||||||
|
@ -1238,13 +1220,13 @@ export default class SendDetails extends Component {
|
||||||
text: loc._.ok,
|
text: loc._.ok,
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
const recipient = this.state.addresses[this.state.recipientsScrollIndex];
|
const recipient = this.state.addresses[this.scrollViewCurrentIndex()];
|
||||||
recipient.amount = BitcoinUnit.MAX;
|
recipient.amount = BitcoinUnit.MAX;
|
||||||
recipient.amountSats = BitcoinUnit.MAX;
|
recipient.amountSats = BitcoinUnit.MAX;
|
||||||
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||||
this.setState({
|
this.setState({
|
||||||
addresses: [recipient],
|
addresses: [recipient],
|
||||||
units: [BitcoinUnit.BTC],
|
units: [BitcoinUnit.BTC],
|
||||||
recipientsScrollIndex: 0,
|
|
||||||
isAdvancedTransactionOptionsVisible: false,
|
isAdvancedTransactionOptionsVisible: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1271,6 +1253,8 @@ export default class SendDetails extends Component {
|
||||||
this.setState({ width: e.nativeEvent.layout.width });
|
this.setState({ width: e.nativeEvent.layout.width });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
keyExtractor = (_item, index) => `${index}`;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.isLoading || typeof this.state.fromWallet === 'undefined') {
|
if (this.state.isLoading || typeof this.state.fromWallet === 'undefined') {
|
||||||
return (
|
return (
|
||||||
|
@ -1286,20 +1270,20 @@ export default class SendDetails extends Component {
|
||||||
<StatusBar barStyle="light-content" />
|
<StatusBar barStyle="light-content" />
|
||||||
<View>
|
<View>
|
||||||
<KeyboardAvoidingView behavior="position">
|
<KeyboardAvoidingView behavior="position">
|
||||||
<ScrollView
|
<FlatList
|
||||||
pagingEnabled
|
|
||||||
horizontal
|
|
||||||
contentContainerStyle={styles.scrollViewContent}
|
|
||||||
ref={ref => (this.scrollView = ref)}
|
|
||||||
keyboardShouldPersistTaps="always"
|
keyboardShouldPersistTaps="always"
|
||||||
onContentSizeChange={() => this.scrollView.scrollToEnd()}
|
|
||||||
onLayout={() => this.scrollView.scrollToEnd()}
|
|
||||||
onMomentumScrollEnd={this.handlePageChange}
|
|
||||||
scrollEnabled={this.state.addresses.length > 1}
|
scrollEnabled={this.state.addresses.length > 1}
|
||||||
|
extraData={this.state.addresses}
|
||||||
|
data={this.state.addresses}
|
||||||
|
renderItem={this.renderBitcoinTransactionInfoFields}
|
||||||
|
keyExtractor={this.keyExtractor}
|
||||||
|
ref={this.scrollView}
|
||||||
|
horizontal
|
||||||
|
pagingEnabled
|
||||||
|
onMomentumScrollBegin={Keyboard.dismiss}
|
||||||
scrollIndicatorInsets={{ top: 0, left: 8, bottom: 0, right: 8 }}
|
scrollIndicatorInsets={{ top: 0, left: 8, bottom: 0, right: 8 }}
|
||||||
>
|
contentContainerStyle={styles.scrollViewContent}
|
||||||
{this.renderBitcoinTransactionInfoFields()}
|
/>
|
||||||
</ScrollView>
|
|
||||||
<View hide={!this.state.showMemoRow} style={styles.memo}>
|
<View hide={!this.state.showMemoRow} style={styles.memo}>
|
||||||
<TextInput
|
<TextInput
|
||||||
onChangeText={text => this.setState({ memo: text })}
|
onChangeText={text => this.setState({ memo: text })}
|
||||||
|
|
Loading…
Add table
Reference in a new issue