mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-21 14:34:55 +01:00
REF: ReplaceFeeSuggestions to TSX
This commit is contained in:
parent
6151f736ca
commit
281570e4b0
4 changed files with 222 additions and 173 deletions
|
@ -1,8 +1,6 @@
|
|||
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, forwardRef } from 'react';
|
||||
import React, { forwardRef } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Dimensions,
|
||||
|
@ -18,9 +16,8 @@ import {
|
|||
} from 'react-native';
|
||||
import { Icon, Text } from 'react-native-elements';
|
||||
|
||||
import { BlueCurrentTheme, useTheme } from './components/themes';
|
||||
import loc, { formatStringAddTwoWhiteSpaces } from './loc';
|
||||
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees';
|
||||
import { useTheme } from './components/themes';
|
||||
import loc from './loc';
|
||||
|
||||
const { height, width } = Dimensions.get('window');
|
||||
const aspectRatio = height / width;
|
||||
|
@ -203,165 +200,6 @@ export const BlueLoading = props => {
|
|||
);
|
||||
};
|
||||
|
||||
export class BlueReplaceFeeSuggestions extends Component {
|
||||
static propTypes = {
|
||||
onFeeSelected: PropTypes.func.isRequired,
|
||||
transactionMinimum: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
transactionMinimum: 1,
|
||||
};
|
||||
|
||||
state = {
|
||||
customFeeValue: '1',
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
try {
|
||||
const cachedNetworkTransactionFees = JSON.parse(await AsyncStorage.getItem(NetworkTransactionFee.StorageKey));
|
||||
|
||||
if (cachedNetworkTransactionFees && 'fastestFee' in cachedNetworkTransactionFees) {
|
||||
this.setState({ networkFees: cachedNetworkTransactionFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST));
|
||||
}
|
||||
} catch (_) {}
|
||||
const networkFees = await NetworkTransactionFees.recommendedFees();
|
||||
this.setState({ networkFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST));
|
||||
}
|
||||
|
||||
onFeeSelected = selectedFeeType => {
|
||||
if (selectedFeeType !== NetworkTransactionFeeType.CUSTOM) {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
if (selectedFeeType === NetworkTransactionFeeType.FAST) {
|
||||
this.props.onFeeSelected(this.state.networkFees.fastestFee);
|
||||
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.fastestFee));
|
||||
} else if (selectedFeeType === NetworkTransactionFeeType.MEDIUM) {
|
||||
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.mediumFee));
|
||||
} else if (selectedFeeType === NetworkTransactionFeeType.SLOW) {
|
||||
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.slowFee));
|
||||
} else if (selectedFeeType === NetworkTransactionFeeType.CUSTOM) {
|
||||
this.props.onFeeSelected(Number(this.state.customFeeValue));
|
||||
}
|
||||
};
|
||||
|
||||
onCustomFeeTextChange = customFee => {
|
||||
const customFeeValue = customFee.replace(/[^0-9]/g, '');
|
||||
this.setState({ customFeeValue, selectedFeeType: NetworkTransactionFeeType.CUSTOM }, () => {
|
||||
this.onFeeSelected(NetworkTransactionFeeType.CUSTOM);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { networkFees, selectedFeeType } = this.state;
|
||||
|
||||
return (
|
||||
<View>
|
||||
{networkFees &&
|
||||
[
|
||||
{
|
||||
label: loc.send.fee_fast,
|
||||
time: loc.send.fee_10m,
|
||||
type: NetworkTransactionFeeType.FAST,
|
||||
rate: networkFees.fastestFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.FAST,
|
||||
},
|
||||
{
|
||||
label: formatStringAddTwoWhiteSpaces(loc.send.fee_medium),
|
||||
time: loc.send.fee_3h,
|
||||
type: NetworkTransactionFeeType.MEDIUM,
|
||||
rate: networkFees.mediumFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.MEDIUM,
|
||||
},
|
||||
{
|
||||
label: loc.send.fee_slow,
|
||||
time: loc.send.fee_1d,
|
||||
type: NetworkTransactionFeeType.SLOW,
|
||||
rate: networkFees.slowFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.SLOW,
|
||||
},
|
||||
].map(({ label, type, time, rate, active }, index) => (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
key={label}
|
||||
onPress={() => this.onFeeSelected(type)}
|
||||
style={[
|
||||
{ paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 },
|
||||
active && { borderRadius: 8, backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor },
|
||||
]}
|
||||
>
|
||||
<View style={{ justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 22, color: BlueCurrentTheme.colors.successColor, fontWeight: '600' }}>{label}</Text>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: BlueCurrentTheme.colors.successColor,
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 3,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: BlueCurrentTheme.colors.background }}>~{time}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ justifyContent: 'flex-end', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={{ color: BlueCurrentTheme.colors.successColor }}>{rate} sat/byte</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={() => this.customTextInput.focus()}
|
||||
style={[
|
||||
{ paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 },
|
||||
selectedFeeType === NetworkTransactionFeeType.CUSTOM && {
|
||||
borderRadius: 8,
|
||||
backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={{ justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 22, color: BlueCurrentTheme.colors.successColor, fontWeight: '600' }}>
|
||||
{formatStringAddTwoWhiteSpaces(loc.send.fee_custom)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center', marginTop: 5 }}>
|
||||
<TextInput
|
||||
onChangeText={this.onCustomFeeTextChange}
|
||||
keyboardType="numeric"
|
||||
value={this.state.customFeeValue}
|
||||
ref={ref => (this.customTextInput = ref)}
|
||||
maxLength={9}
|
||||
style={{
|
||||
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
|
||||
borderBottomColor: BlueCurrentTheme.colors.formBorder,
|
||||
borderBottomWidth: 0.5,
|
||||
borderColor: BlueCurrentTheme.colors.formBorder,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1.0,
|
||||
color: '#81868e',
|
||||
flex: 1,
|
||||
marginRight: 10,
|
||||
minHeight: 33,
|
||||
paddingRight: 5,
|
||||
paddingLeft: 5,
|
||||
}}
|
||||
onFocus={() => this.onCustomFeeTextChange(this.state.customFeeValue)}
|
||||
defaultValue={this.props.transactionMinimum}
|
||||
placeholder={loc.send.fee_satvbyte}
|
||||
placeholderTextColor="#81868e"
|
||||
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
|
||||
/>
|
||||
<Text style={{ color: BlueCurrentTheme.colors.successColor }}>sat/byte</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<BlueText style={{ color: BlueCurrentTheme.colors.alternativeTextColor }}>
|
||||
{loc.formatString(loc.send.fee_replace_minvb, { min: this.props.transactionMinimum })}
|
||||
</BlueText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function BlueBigCheckmark({ style = {} }) {
|
||||
const defaultStyles = {
|
||||
backgroundColor: '#ccddf9',
|
||||
|
|
210
components/ReplaceFeeSuggestions.tsx
Normal file
210
components/ReplaceFeeSuggestions.tsx
Normal file
|
@ -0,0 +1,210 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { View, Text, TextInput, TouchableOpacity, Keyboard, StyleSheet } from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { BlueText } from '../BlueComponents';
|
||||
import loc, { formatStringAddTwoWhiteSpaces } from '../loc';
|
||||
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from '../models/networkTransactionFees';
|
||||
import { useTheme } from './themes';
|
||||
|
||||
interface ReplaceFeeSuggestionsProps {
|
||||
onFeeSelected: (fee: number) => void;
|
||||
transactionMinimum?: number;
|
||||
}
|
||||
|
||||
const ReplaceFeeSuggestions: React.FC<ReplaceFeeSuggestionsProps> = ({ onFeeSelected, transactionMinimum = 1 }) => {
|
||||
const [networkFees, setNetworkFees] = useState<NetworkTransactionFee | null>(null);
|
||||
const [selectedFeeType, setSelectedFeeType] = useState<NetworkTransactionFeeType>(NetworkTransactionFeeType.FAST);
|
||||
const [customFeeValue, setCustomFeeValue] = useState<string>('1');
|
||||
const customTextInput = useRef<TextInput>(null);
|
||||
const { colors } = useTheme();
|
||||
const stylesHook = StyleSheet.create({
|
||||
activeButton: {
|
||||
backgroundColor: colors.incomingBackgroundColor,
|
||||
},
|
||||
buttonText: {
|
||||
color: colors.successColor,
|
||||
},
|
||||
timeContainer: {
|
||||
backgroundColor: colors.successColor,
|
||||
},
|
||||
timeText: {
|
||||
color: colors.background,
|
||||
},
|
||||
rateText: {
|
||||
color: colors.successColor,
|
||||
},
|
||||
customFeeInput: {
|
||||
backgroundColor: colors.inputBackgroundColor,
|
||||
borderBottomColor: colors.formBorder,
|
||||
borderColor: colors.formBorder,
|
||||
},
|
||||
alternativeText: {
|
||||
color: colors.alternativeTextColor,
|
||||
},
|
||||
});
|
||||
|
||||
const fetchNetworkFees = useCallback(async () => {
|
||||
try {
|
||||
const cachedNetworkTransactionFees = JSON.parse((await AsyncStorage.getItem(NetworkTransactionFee.StorageKey)) || '{}');
|
||||
|
||||
if (cachedNetworkTransactionFees && 'fastestFee' in cachedNetworkTransactionFees) {
|
||||
setNetworkFees(cachedNetworkTransactionFees);
|
||||
onFeeSelected(cachedNetworkTransactionFees.fastestFee);
|
||||
setSelectedFeeType(NetworkTransactionFeeType.FAST);
|
||||
}
|
||||
} catch (_) {}
|
||||
const fees = await NetworkTransactionFees.recommendedFees();
|
||||
setNetworkFees(fees);
|
||||
onFeeSelected(fees.fastestFee);
|
||||
setSelectedFeeType(NetworkTransactionFeeType.FAST);
|
||||
}, [onFeeSelected]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchNetworkFees();
|
||||
}, [fetchNetworkFees]);
|
||||
|
||||
const handleFeeSelection = (feeType: NetworkTransactionFeeType) => {
|
||||
if (feeType !== NetworkTransactionFeeType.CUSTOM) {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
if (networkFees) {
|
||||
let selectedFee: number;
|
||||
switch (feeType) {
|
||||
case NetworkTransactionFeeType.FAST:
|
||||
selectedFee = networkFees.fastestFee;
|
||||
break;
|
||||
case NetworkTransactionFeeType.MEDIUM:
|
||||
selectedFee = networkFees.mediumFee;
|
||||
break;
|
||||
case NetworkTransactionFeeType.SLOW:
|
||||
selectedFee = networkFees.slowFee;
|
||||
break;
|
||||
case NetworkTransactionFeeType.CUSTOM:
|
||||
selectedFee = Number(customFeeValue);
|
||||
break;
|
||||
}
|
||||
onFeeSelected(selectedFee);
|
||||
setSelectedFeeType(feeType);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCustomFeeChange = (customFee: string) => {
|
||||
const sanitizedFee = customFee.replace(/[^0-9]/g, '');
|
||||
setCustomFeeValue(sanitizedFee);
|
||||
handleFeeSelection(NetworkTransactionFeeType.CUSTOM);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
{networkFees &&
|
||||
[
|
||||
{
|
||||
label: loc.send.fee_fast,
|
||||
time: loc.send.fee_10m,
|
||||
type: NetworkTransactionFeeType.FAST,
|
||||
rate: networkFees.fastestFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.FAST,
|
||||
},
|
||||
{
|
||||
label: formatStringAddTwoWhiteSpaces(loc.send.fee_medium),
|
||||
time: loc.send.fee_3h,
|
||||
type: NetworkTransactionFeeType.MEDIUM,
|
||||
rate: networkFees.mediumFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.MEDIUM,
|
||||
},
|
||||
{
|
||||
label: loc.send.fee_slow,
|
||||
time: loc.send.fee_1d,
|
||||
type: NetworkTransactionFeeType.SLOW,
|
||||
rate: networkFees.slowFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.SLOW,
|
||||
},
|
||||
].map(({ label, type, time, rate, active }) => (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
key={label}
|
||||
onPress={() => handleFeeSelection(type)}
|
||||
style={[styles.button, active && stylesHook.activeButton]}
|
||||
>
|
||||
<View style={styles.buttonContent}>
|
||||
<Text style={[styles.buttonText, stylesHook.buttonText]}>{label}</Text>
|
||||
<View style={[styles.timeContainer, stylesHook.timeContainer]}>
|
||||
<Text style={stylesHook.timeText}>~{time}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.rateContainer}>
|
||||
<Text style={stylesHook.rateText}>{rate} sat/byte</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={() => customTextInput.current?.focus()}
|
||||
style={[styles.button, selectedFeeType === NetworkTransactionFeeType.CUSTOM && stylesHook.activeButton]}
|
||||
>
|
||||
<View style={styles.buttonContent}>
|
||||
<Text style={[styles.buttonText, stylesHook.buttonText]}>{formatStringAddTwoWhiteSpaces(loc.send.fee_custom)}</Text>
|
||||
</View>
|
||||
<View style={[styles.buttonContent, styles.customFeeInputContainer]}>
|
||||
<TextInput
|
||||
onChangeText={handleCustomFeeChange}
|
||||
keyboardType="numeric"
|
||||
value={customFeeValue}
|
||||
ref={customTextInput}
|
||||
maxLength={9}
|
||||
style={[styles.customFeeInput, stylesHook.customFeeInput]}
|
||||
onFocus={() => handleCustomFeeChange(customFeeValue)}
|
||||
placeholder={loc.send.fee_satvbyte}
|
||||
placeholderTextColor="#81868e"
|
||||
/>
|
||||
<Text style={stylesHook.rateText}>sat/byte</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<BlueText style={stylesHook.alternativeText}>{loc.formatString(loc.send.fee_replace_minvb, { min: transactionMinimum })}</BlueText>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
marginBottom: 10,
|
||||
borderRadius: 8,
|
||||
},
|
||||
buttonContent: {
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 22,
|
||||
fontWeight: '600',
|
||||
},
|
||||
timeContainer: {
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 3,
|
||||
},
|
||||
rateContainer: {
|
||||
justifyContent: 'flex-end',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
customFeeInputContainer: {
|
||||
marginTop: 5,
|
||||
},
|
||||
customFeeInput: {
|
||||
borderBottomWidth: 0.5,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1.0,
|
||||
color: '#81868e',
|
||||
flex: 1,
|
||||
marginRight: 10,
|
||||
minHeight: 33,
|
||||
paddingRight: 5,
|
||||
paddingLeft: 5,
|
||||
},
|
||||
});
|
||||
|
||||
export default ReplaceFeeSuggestions;
|
|
@ -1,11 +1,11 @@
|
|||
import * as BlueElectrum from '../blue_modules/BlueElectrum';
|
||||
|
||||
export const NetworkTransactionFeeType = Object.freeze({
|
||||
FAST: 'Fast',
|
||||
MEDIUM: 'MEDIUM',
|
||||
SLOW: 'SLOW',
|
||||
CUSTOM: 'CUSTOM',
|
||||
});
|
||||
export enum NetworkTransactionFeeType {
|
||||
FAST = 'Fast',
|
||||
MEDIUM = 'MEDIUM',
|
||||
SLOW = 'SLOW',
|
||||
CUSTOM = 'CUSTOM',
|
||||
}
|
||||
|
||||
export class NetworkTransactionFee {
|
||||
static StorageKey = 'NetworkTransactionFee';
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Text } from 'react-native-elements';
|
|||
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { BlueCard, BlueReplaceFeeSuggestions, BlueSpacing, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { BlueCard, BlueSpacing, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { HDSegwitBech32Transaction, HDSegwitBech32Wallet } from '../../class';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
|
@ -26,6 +26,7 @@ import { BlueCurrentTheme } from '../../components/themes';
|
|||
import loc from '../../loc';
|
||||
import { StorageContext } from '../../components/Context/StorageProvider';
|
||||
import { popToTop } from '../../NavigationService';
|
||||
import ReplaceFeeSuggestions from '../../components/ReplaceFeeSuggestions';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
|
@ -167,7 +168,7 @@ export default class CPFP extends Component {
|
|||
<BlueCard style={styles.center}>
|
||||
<BlueText>{text}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
<BlueReplaceFeeSuggestions onFeeSelected={fee => this.setState({ newFeeRate: fee })} transactionMinimum={this.state.feeRate} />
|
||||
<ReplaceFeeSuggestions onFeeSelected={fee => this.setState({ newFeeRate: fee })} transactionMinimum={this.state.feeRate} />
|
||||
<BlueSpacing />
|
||||
<Button
|
||||
disabled={this.state.newFeeRate <= this.state.feeRate}
|
||||
|
|
Loading…
Add table
Reference in a new issue