mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 09:50:15 +01:00
commit
14476255ac
377
components/SelectFeeModal.tsx
Normal file
377
components/SelectFeeModal.tsx
Normal file
@ -0,0 +1,377 @@
|
||||
import React, { useState, useRef, forwardRef, useImperativeHandle } from 'react';
|
||||
import { View, Text, TouchableOpacity, TextInput, StyleSheet, Platform } from 'react-native';
|
||||
import BottomModal, { BottomModalHandle } from './BottomModal';
|
||||
import { useTheme } from './themes';
|
||||
import loc from '../loc';
|
||||
import { SecondButton } from './SecondButton';
|
||||
|
||||
interface NetworkTransactionFees {
|
||||
fastestFee: number;
|
||||
mediumFee: number;
|
||||
slowFee: number;
|
||||
}
|
||||
|
||||
interface FeePrecalc {
|
||||
fastestFee: number | null;
|
||||
mediumFee: number | null;
|
||||
slowFee: number | null;
|
||||
current: number | null;
|
||||
}
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
time: string;
|
||||
fee: number | null;
|
||||
rate: number;
|
||||
active: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface SelectFeeModalProps {
|
||||
networkTransactionFees: NetworkTransactionFees;
|
||||
feePrecalc: FeePrecalc;
|
||||
feeRate: number | string;
|
||||
setCustomFee: (fee: string) => void;
|
||||
setFeePrecalc: (fn: (fp: FeePrecalc) => FeePrecalc) => void;
|
||||
}
|
||||
|
||||
const SelectFeeModal = forwardRef<BottomModalHandle, SelectFeeModalProps>(
|
||||
({ networkTransactionFees, feePrecalc, feeRate, setCustomFee, setFeePrecalc }, ref) => {
|
||||
const [customFee, setCustomFeeState] = useState('');
|
||||
const feeModalRef = useRef<BottomModalHandle>(null);
|
||||
const customModalRef = useRef<BottomModalHandle>(null);
|
||||
const nf = networkTransactionFees;
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
loading: {
|
||||
backgroundColor: colors.background,
|
||||
},
|
||||
root: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
feeModalItemActive: {
|
||||
backgroundColor: colors.feeActive,
|
||||
},
|
||||
feeModalLabel: {
|
||||
color: colors.successColor,
|
||||
},
|
||||
feeModalTime: {
|
||||
backgroundColor: colors.successColor,
|
||||
},
|
||||
feeModalTimeText: {
|
||||
color: colors.background,
|
||||
},
|
||||
feeModalValue: {
|
||||
color: colors.successColor,
|
||||
},
|
||||
feeModalCustomText: {
|
||||
color: colors.buttonAlternativeTextColor,
|
||||
},
|
||||
selectLabel: {
|
||||
color: colors.buttonTextColor,
|
||||
},
|
||||
of: {
|
||||
color: colors.feeText,
|
||||
},
|
||||
memo: {
|
||||
borderColor: colors.formBorder,
|
||||
borderBottomColor: colors.formBorder,
|
||||
backgroundColor: colors.inputBackgroundColor,
|
||||
},
|
||||
feeLabel: {
|
||||
color: colors.feeText,
|
||||
},
|
||||
feeModalItemDisabled: {
|
||||
backgroundColor: colors.buttonDisabledBackgroundColor,
|
||||
},
|
||||
feeModalItemTextDisabled: {
|
||||
color: colors.buttonDisabledTextColor,
|
||||
},
|
||||
feeRow: {
|
||||
backgroundColor: colors.feeLabel,
|
||||
},
|
||||
feeValue: {
|
||||
color: colors.feeValue,
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
present: async () => feeModalRef.current?.present(),
|
||||
dismiss: async () => feeModalRef.current?.dismiss(),
|
||||
}));
|
||||
|
||||
const options: Option[] = [
|
||||
{
|
||||
label: loc.send.fee_fast,
|
||||
time: loc.send.fee_10m,
|
||||
fee: feePrecalc.fastestFee,
|
||||
rate: nf.fastestFee,
|
||||
active: Number(feeRate) === nf.fastestFee,
|
||||
},
|
||||
{
|
||||
label: loc.send.fee_medium,
|
||||
time: loc.send.fee_3h,
|
||||
fee: feePrecalc.mediumFee,
|
||||
rate: nf.mediumFee,
|
||||
active: Number(feeRate) === nf.mediumFee,
|
||||
disabled: nf.mediumFee === nf.fastestFee,
|
||||
},
|
||||
{
|
||||
label: loc.send.fee_slow,
|
||||
time: loc.send.fee_1d,
|
||||
fee: feePrecalc.slowFee,
|
||||
rate: nf.slowFee,
|
||||
active: Number(feeRate) === nf.slowFee,
|
||||
disabled: nf.slowFee === nf.mediumFee || nf.slowFee === nf.fastestFee,
|
||||
},
|
||||
];
|
||||
|
||||
const formatFee = (fee: number | null): string => {
|
||||
return fee ? `${fee} sat/vB` : '';
|
||||
};
|
||||
|
||||
const handleCustomFeeSubmit = async () => {
|
||||
if (!/^\d+$/.test(customFee) || Number(customFee) <= 0) {
|
||||
// Handle error if necessary
|
||||
return;
|
||||
}
|
||||
const fee = Number(customFee) < 1 ? '1' : customFee;
|
||||
setCustomFee(fee);
|
||||
await customModalRef.current?.dismiss();
|
||||
await feeModalRef.current?.dismiss();
|
||||
};
|
||||
|
||||
const handleCancel = async () => {
|
||||
setCustomFeeState('');
|
||||
await customModalRef.current?.dismiss();
|
||||
};
|
||||
|
||||
const handlePressCustom = async () => {
|
||||
await customModalRef.current?.present();
|
||||
};
|
||||
|
||||
const handleSelectOption = async (fee: number | null, rate: number) => {
|
||||
setFeePrecalc(fp => ({ ...fp, current: fee }));
|
||||
await feeModalRef.current?.dismiss();
|
||||
setCustomFee(rate.toString());
|
||||
};
|
||||
|
||||
return (
|
||||
<BottomModal
|
||||
ref={feeModalRef}
|
||||
backgroundColor={colors.modal}
|
||||
contentContainerStyle={[styles.modalContent, styles.modalContentMinHeight]}
|
||||
footerDefaultMargins
|
||||
footer={
|
||||
<View style={styles.feeModalFooter}>
|
||||
<TouchableOpacity testID="feeCustom" accessibilityRole="button" onPress={handlePressCustom}>
|
||||
<Text style={[styles.feeModalCustomText, stylesHook.feeModalCustomText]}>{loc.send.fee_custom}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<View style={styles.paddingTop66}>
|
||||
{options.map(({ label, time, fee, rate, active, disabled }) => (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
key={label}
|
||||
disabled={disabled}
|
||||
onPress={() => handleSelectOption(fee, rate)}
|
||||
style={[styles.feeModalItem, active && styles.feeModalItemActive, active && !disabled && stylesHook.feeModalItemActive]}
|
||||
>
|
||||
<View style={styles.feeModalRow}>
|
||||
<Text style={[styles.feeModalLabel, disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalLabel]}>
|
||||
{label}
|
||||
</Text>
|
||||
<View style={[styles.feeModalTime, disabled ? stylesHook.feeModalItemDisabled : stylesHook.feeModalTime]}>
|
||||
<Text style={stylesHook.feeModalTimeText}>~{time}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.feeModalRow}>
|
||||
<Text style={disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalValue}>{fee && formatFee(fee)}</Text>
|
||||
<Text style={disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalValue}>
|
||||
{rate} {loc.units.sat_vbyte}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<BottomModal
|
||||
ref={customModalRef}
|
||||
backgroundColor={colors.modal}
|
||||
showCloseButton={false}
|
||||
contentContainerStyle={[styles.modalContent, styles.modalContentMinHeight]}
|
||||
footer={
|
||||
<View style={[styles.feeModalFooter, styles.feeModalFooterSpacing]}>
|
||||
<SecondButton title={loc._.cancel} onPress={handleCancel} />
|
||||
<View style={styles.feeModalFooterSpacing} />
|
||||
<SecondButton title={loc._.ok} onPress={handleCustomFeeSubmit} disabled={!customFee || Number(customFee) <= 0} />
|
||||
</View>
|
||||
}
|
||||
footerDefaultMargins
|
||||
>
|
||||
<View style={styles.paddingTop30}>
|
||||
<Text style={[styles.feeModalLabel, stylesHook.feeModalLabel]}>{loc.send.insert_custom_fee}</Text>
|
||||
<View style={styles.optionsContent} />
|
||||
<TextInput
|
||||
style={[styles.feeModalLabel, stylesHook.feeModalLabel]}
|
||||
keyboardType="numeric"
|
||||
placeholder={loc.send.create_fee}
|
||||
value={customFee}
|
||||
onChangeText={setCustomFeeState}
|
||||
autoFocus
|
||||
/>
|
||||
</View>
|
||||
</BottomModal>
|
||||
</BottomModal>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default SelectFeeModal;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loading: {
|
||||
flex: 1,
|
||||
paddingTop: 20,
|
||||
},
|
||||
root: {
|
||||
flex: 1,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
scrollViewContent: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
scrollViewIndicator: {
|
||||
top: 0,
|
||||
left: 8,
|
||||
bottom: 0,
|
||||
right: 8,
|
||||
},
|
||||
modalContent: {
|
||||
margin: 22,
|
||||
},
|
||||
modalContentMinHeight: Platform.OS === 'android' ? { minHeight: 400 } : {},
|
||||
paddingTop66: { paddingVertical: 66 },
|
||||
paddingTop30: { paddingBottom: 60, paddingTop: 30 },
|
||||
optionsContent: {
|
||||
padding: 22,
|
||||
},
|
||||
feeModalItem: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
marginBottom: 10,
|
||||
},
|
||||
feeModalItemActive: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
feeModalRow: {
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
feeModalLabel: {
|
||||
fontSize: 22,
|
||||
fontWeight: '600',
|
||||
},
|
||||
feeModalTime: {
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 3,
|
||||
},
|
||||
feeModalCustomText: {
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
},
|
||||
createButton: {
|
||||
marginVertical: 16,
|
||||
marginHorizontal: 16,
|
||||
alignContent: 'center',
|
||||
minHeight: 44,
|
||||
},
|
||||
select: {
|
||||
marginBottom: 24,
|
||||
marginHorizontal: 24,
|
||||
alignItems: 'center',
|
||||
},
|
||||
selectTouch: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
selectText: {
|
||||
color: '#9aa0aa',
|
||||
fontSize: 14,
|
||||
marginRight: 8,
|
||||
},
|
||||
selectWrap: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: 4,
|
||||
},
|
||||
selectLabel: {
|
||||
fontSize: 14,
|
||||
},
|
||||
of: {
|
||||
alignSelf: 'flex-end',
|
||||
marginRight: 18,
|
||||
marginVertical: 8,
|
||||
},
|
||||
feeModalFooter: {
|
||||
paddingBottom: 36,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
feeModalFooterSpacing: {
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
memo: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
borderBottomWidth: 0.5,
|
||||
minHeight: 44,
|
||||
height: 44,
|
||||
marginHorizontal: 20,
|
||||
alignItems: 'center',
|
||||
marginVertical: 8,
|
||||
borderRadius: 4,
|
||||
},
|
||||
memoText: {
|
||||
flex: 1,
|
||||
marginHorizontal: 8,
|
||||
minHeight: 33,
|
||||
color: '#81868e',
|
||||
},
|
||||
fee: {
|
||||
flexDirection: 'row',
|
||||
marginHorizontal: 20,
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
feeLabel: {
|
||||
fontSize: 14,
|
||||
},
|
||||
feeRow: {
|
||||
minWidth: 40,
|
||||
height: 25,
|
||||
borderRadius: 4,
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
advancedOptions: {
|
||||
minWidth: 40,
|
||||
height: 40,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
feeModalCloseButton: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
feeModalCloseButtonText: {
|
||||
color: '#007AFF',
|
||||
},
|
||||
});
|
@ -169,6 +169,7 @@
|
||||
"fee_1d": "1d",
|
||||
"fee_3h": "3h",
|
||||
"fee_custom": "Custom",
|
||||
"insert_custom_fee": "Insert custom fee",
|
||||
"fee_fast": "Fast",
|
||||
"fee_medium": "Medium",
|
||||
"fee_replace_minvb": "The total fee rate (satoshi per vByte) you want to pay should be higher than {min} sat/vByte.",
|
||||
|
@ -43,7 +43,6 @@ import InputAccessoryAllFunds from '../../components/InputAccessoryAllFunds';
|
||||
import ListItem from '../../components/ListItem';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import ToolTipMenu from '../../components/TooltipMenu';
|
||||
import prompt from '../../helpers/prompt';
|
||||
import { requestCameraAuthorization, scanQrHelper } from '../../helpers/scan-qr';
|
||||
import loc, { formatBalance, formatBalanceWithoutSuffix } from '../../loc';
|
||||
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
|
||||
@ -58,6 +57,7 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
||||
import { ContactList } from '../../class/contact-list';
|
||||
import { useStorage } from '../../hooks/context/useStorage';
|
||||
import { Action } from '../../components/types';
|
||||
import SelectFeeModal from '../../components/SelectFeeModal';
|
||||
|
||||
interface IPaymentDestinations {
|
||||
address: string; // btc address or payment code
|
||||
@ -1164,24 +1164,7 @@ const SendDetails = () => {
|
||||
root: {
|
||||
backgroundColor: colors.elevated,
|
||||
},
|
||||
feeModalItemActive: {
|
||||
backgroundColor: colors.feeActive,
|
||||
},
|
||||
feeModalLabel: {
|
||||
color: colors.successColor,
|
||||
},
|
||||
feeModalTime: {
|
||||
backgroundColor: colors.successColor,
|
||||
},
|
||||
feeModalTimeText: {
|
||||
color: colors.background,
|
||||
},
|
||||
feeModalValue: {
|
||||
color: colors.successColor,
|
||||
},
|
||||
feeModalCustomText: {
|
||||
color: colors.buttonAlternativeTextColor,
|
||||
},
|
||||
|
||||
selectLabel: {
|
||||
color: colors.buttonTextColor,
|
||||
},
|
||||
@ -1196,12 +1179,7 @@ const SendDetails = () => {
|
||||
feeLabel: {
|
||||
color: colors.feeText,
|
||||
},
|
||||
feeModalItemDisabled: {
|
||||
backgroundColor: colors.buttonDisabledBackgroundColor,
|
||||
},
|
||||
feeModalItemTextDisabled: {
|
||||
color: colors.buttonDisabledTextColor,
|
||||
},
|
||||
|
||||
feeRow: {
|
||||
backgroundColor: colors.feeLabel,
|
||||
},
|
||||
@ -1216,108 +1194,6 @@ const SendDetails = () => {
|
||||
return totalWithFee;
|
||||
};
|
||||
|
||||
const renderFeeSelectionModal = () => {
|
||||
const nf = networkTransactionFees;
|
||||
const options = [
|
||||
{
|
||||
label: loc.send.fee_fast,
|
||||
time: loc.send.fee_10m,
|
||||
fee: feePrecalc.fastestFee,
|
||||
rate: nf.fastestFee,
|
||||
active: Number(feeRate) === nf.fastestFee,
|
||||
},
|
||||
{
|
||||
label: loc.send.fee_medium,
|
||||
time: loc.send.fee_3h,
|
||||
fee: feePrecalc.mediumFee,
|
||||
rate: nf.mediumFee,
|
||||
active: Number(feeRate) === nf.mediumFee,
|
||||
disabled: nf.mediumFee === nf.fastestFee,
|
||||
},
|
||||
{
|
||||
label: loc.send.fee_slow,
|
||||
time: loc.send.fee_1d,
|
||||
fee: feePrecalc.slowFee,
|
||||
rate: nf.slowFee,
|
||||
active: Number(feeRate) === nf.slowFee,
|
||||
disabled: nf.slowFee === nf.mediumFee || nf.slowFee === nf.fastestFee,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<BottomModal
|
||||
ref={feeModalRef}
|
||||
backgroundColor={colors.modal}
|
||||
contentContainerStyle={[styles.modalContent, styles.modalContentMinHeight]}
|
||||
footerDefaultMargins
|
||||
footer={
|
||||
<View style={styles.feeModalFooter}>
|
||||
<TouchableOpacity
|
||||
testID="feeCustom"
|
||||
accessibilityRole="button"
|
||||
onPress={async () => {
|
||||
await feeModalRef.current?.dismiss();
|
||||
let error = loc.send.fee_satvbyte;
|
||||
while (true) {
|
||||
let fee: number | string;
|
||||
|
||||
try {
|
||||
fee = await prompt(loc.send.create_fee, error, true, 'numeric');
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(fee)) {
|
||||
error = loc.send.details_fee_field_is_not_valid;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Number(fee) < 1) fee = '1';
|
||||
fee = Number(fee).toString(); // this will remove leading zeros if any
|
||||
setCustomFee(fee);
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text style={[styles.feeModalCustomText, stylesHook.feeModalCustomText]}>{loc.send.fee_custom}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<View style={styles.paddingTop80}>
|
||||
{options.map(({ label, time, fee, rate, active, disabled }) => (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
key={label}
|
||||
disabled={disabled}
|
||||
onPress={() => {
|
||||
setFeePrecalc(fp => ({ ...fp, current: fee }));
|
||||
feeModalRef.current?.dismiss();
|
||||
setCustomFee(rate.toString());
|
||||
}}
|
||||
style={[styles.feeModalItem, active && styles.feeModalItemActive, active && !disabled && stylesHook.feeModalItemActive]}
|
||||
>
|
||||
<View style={styles.feeModalRow}>
|
||||
<Text style={[styles.feeModalLabel, disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalLabel]}>
|
||||
{label}
|
||||
</Text>
|
||||
<View style={[styles.feeModalTime, disabled ? stylesHook.feeModalItemDisabled : stylesHook.feeModalTime]}>
|
||||
<Text style={stylesHook.feeModalTimeText}>~{time}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.feeModalRow}>
|
||||
<Text style={disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalValue}>{fee && formatFee(fee)}</Text>
|
||||
<Text style={disabled ? stylesHook.feeModalItemTextDisabled : stylesHook.feeModalValue}>
|
||||
{rate} {loc.units.sat_vbyte}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</BottomModal>
|
||||
);
|
||||
};
|
||||
|
||||
const renderOptionsModal = () => {
|
||||
const isSendMaxUsed = addresses.some(element => element.amount === BitcoinUnit.MAX);
|
||||
|
||||
@ -1591,7 +1467,14 @@ const SendDetails = () => {
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
{renderCreateButton()}
|
||||
{renderFeeSelectionModal()}
|
||||
<SelectFeeModal
|
||||
ref={feeModalRef}
|
||||
networkTransactionFees={networkTransactionFees}
|
||||
feePrecalc={feePrecalc}
|
||||
feeRate={feeRate}
|
||||
setCustomFee={setCustomFee}
|
||||
setFeePrecalc={setFeePrecalc}
|
||||
/>
|
||||
{renderOptionsModal()}
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
@ -1655,40 +1538,11 @@ const styles = StyleSheet.create({
|
||||
bottom: 0,
|
||||
right: 8,
|
||||
},
|
||||
modalContent: {
|
||||
margin: 22,
|
||||
},
|
||||
modalContentMinHeight: Platform.OS === 'android' ? { minHeight: 400 } : {},
|
||||
paddingTop80: { paddingTop: 80 },
|
||||
|
||||
optionsContent: {
|
||||
padding: 22,
|
||||
},
|
||||
feeModalItem: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
marginBottom: 10,
|
||||
},
|
||||
feeModalItemActive: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
feeModalRow: {
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
feeModalLabel: {
|
||||
fontSize: 22,
|
||||
fontWeight: '600',
|
||||
},
|
||||
feeModalTime: {
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 3,
|
||||
},
|
||||
feeModalCustomText: {
|
||||
fontSize: 15,
|
||||
fontWeight: '600',
|
||||
},
|
||||
|
||||
createButton: {
|
||||
marginVertical: 16,
|
||||
marginHorizontal: 16,
|
||||
@ -1722,9 +1576,7 @@ const styles = StyleSheet.create({
|
||||
marginRight: 18,
|
||||
marginVertical: 8,
|
||||
},
|
||||
feeModalFooter: {
|
||||
paddingVertical: 50,
|
||||
},
|
||||
|
||||
memo: {
|
||||
flexDirection: 'row',
|
||||
borderWidth: 1,
|
||||
|
Loading…
Reference in New Issue
Block a user