Files

323 lines
11 KiB
TypeScript

import * as React from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { inject, observer } from 'mobx-react';
import BigNumber from 'bignumber.js';
import Amount from './Amount';
import { Row } from './layout/Row';
import { themeColor } from '../utils/ThemeUtils';
import { getSatAmount } from '../utils/AmountUtils';
import {
SATS_PER_BTC,
formatBitcoinWithSpaces,
numberWithCommas
} from '../utils/UnitsUtils';
import { fiatStore, settingsStore, unitsStore } from '../stores/Stores';
import FiatStore from '../stores/FiatStore';
import SettingsStore from '../stores/SettingsStore';
import UnitsStore from '../stores/UnitsStore';
import ExchangeBitcoinSVG from '../assets/images/SVG/ExchangeBitcoin.svg';
import ExchangeFiatSVG from '../assets/images/SVG/ExchangeFiat.svg';
import NavigationService from '../NavigationService';
interface AmountInputProps {
onAmountChange: (amount: string, satAmount: string | number) => void;
amount?: string;
sats?: string;
locked?: boolean;
title?: string;
hideConversion?: boolean;
hideUnitChangeButton?: boolean;
forceUnit?: 'sats' | 'BTC' | 'fiat';
FiatStore?: FiatStore;
SettingsStore?: SettingsStore;
UnitsStore?: UnitsStore;
prefix?: any;
error?: boolean;
}
interface AmountInputState {
satAmount: string | number;
}
const getAmount = (sats: string | number, forceUnit?: string) => {
const { fiatRates } = fiatStore;
const { settings } = settingsStore;
const { fiat } = settings;
const units = forceUnit || unitsStore.units;
// replace , with . for unit separator
const value = sats ? sats.toString().replace(/,/g, '.') : '';
const fiatEntry =
fiat && fiatRates
? fiatRates.find((entry: any) => entry.code === fiat)
: null;
const rate = fiat && fiatRates && fiatEntry ? fiatEntry.rate : 0;
let amount: string = '';
switch (units) {
case 'sats':
amount = value;
break;
case 'BTC':
amount = new BigNumber(value || 0).div(SATS_PER_BTC).toString();
break;
case 'fiat':
const { decimalPlaces } = fiatStore.getSymbol();
const decimals = decimalPlaces !== undefined ? decimalPlaces : 2;
amount = rate
? new BigNumber(value.toString().replace(/,/g, '.'))
.times(rate)
.div(SATS_PER_BTC)
.toNumber()
.toFixed(decimals)
: '0';
break;
}
return amount;
};
@inject('FiatStore', 'SettingsStore', 'UnitsStore')
@observer
export default class AmountInput extends React.Component<
AmountInputProps,
AmountInputState
> {
constructor(props: any) {
super(props);
const { amount, onAmountChange } = props;
let satAmount = '0';
if (amount)
satAmount = getSatAmount(amount, props.forceUnit).toString();
onAmountChange(amount, satAmount);
this.state = {
satAmount
};
}
componentDidUpdate(prevProps: Readonly<AmountInputProps>): void {
const { amount, forceUnit, onAmountChange } = this.props;
if (amount !== prevProps.amount || forceUnit !== prevProps.forceUnit) {
if (forceUnit === 'sats' && forceUnit !== prevProps.forceUnit) {
const currentSatAmount = getSatAmount(amount || '', forceUnit);
this.setState({ satAmount: currentSatAmount });
onAmountChange(currentSatAmount.toString(), currentSatAmount);
} else {
const satAmount = getSatAmount(amount || '', forceUnit);
if (satAmount !== this.state.satAmount) {
this.setState({ satAmount });
}
}
}
}
onChangeUnits = () => {
const { amount, onAmountChange, UnitsStore }: any = this.props;
UnitsStore.changeUnits();
const satAmount = getSatAmount(amount, this.props.forceUnit);
onAmountChange(amount, satAmount);
this.setState({ satAmount });
};
getDisplayValue = (): string => {
const { amount, sats, forceUnit } = this.props;
return amount !== undefined
? amount
: sats
? getAmount(sats, forceUnit)
: '0';
};
openKeypad = () => {
if (!this.props.locked) {
const { hideUnitChangeButton } = this.props;
let displayValue = this.getDisplayValue();
// Strip trailing zeros after decimal point so the keypad
// doesn't think decimal places are already filled
// e.g. "0.00" → "0", "50.20" → "50.2", "50.25" → "50.25"
if (displayValue.includes('.')) {
displayValue = displayValue
.replace(/0+$/, '')
.replace(/\.$/, '');
}
// Navigate to keypad view with callback
NavigationService.navigate('AmountKeypad', {
initialAmount: displayValue || '0',
hideUnitChangeButton,
forceUnit: this.props.forceUnit,
onConfirm: (newAmount: string) => {
const { onAmountChange, forceUnit } = this.props;
const satAmount = getSatAmount(newAmount, forceUnit);
onAmountChange(newAmount, satAmount);
this.setState({ satAmount });
}
});
}
};
render() {
const { satAmount } = this.state;
const {
title,
locked,
hideConversion,
hideUnitChangeButton,
FiatStore,
UnitsStore,
SettingsStore,
forceUnit,
prefix,
error
} = this.props;
const { units }: any = UnitsStore;
const effectiveUnits = forceUnit || units;
const { getRate }: any = FiatStore;
const { settings }: any = SettingsStore;
const { fiatEnabled } = settings;
const displayValue = this.getDisplayValue();
let formattedAmount: string;
if (effectiveUnits === 'BTC') {
formattedAmount = `₿${formatBitcoinWithSpaces(
displayValue || '0'
)}`;
} else if (effectiveUnits === 'fiat') {
formattedAmount = FiatStore!.formatAmountForDisplay(
displayValue || '0'
);
} else {
const isSingular = parseFloat(displayValue) === 1;
formattedAmount = `${numberWithCommas(displayValue || '0')} ${
isSingular ? 'sat' : 'sats'
}`;
}
return (
<React.Fragment>
{title && (
<Text
style={{
fontFamily: 'PPNeueMontreal-Book',
color: themeColor('secondaryText'),
fontSize: 14,
marginBottom: 5
}}
>
{title}
</Text>
)}
<Row>
{prefix ? prefix : undefined}
<TouchableOpacity
onPress={this.openKeypad}
disabled={locked}
activeOpacity={0.7}
style={[
styles.amountDisplay,
{
backgroundColor: themeColor('secondary'),
borderWidth: error ? 1 : 0,
borderColor: error
? themeColor('error')
: undefined,
opacity: locked ? 0.8 : 1
}
]}
>
<Text
style={[
styles.amountText,
{
color:
displayValue === '0' ||
displayValue === ''
? themeColor('secondaryText')
: themeColor('text')
}
]}
>
{formattedAmount}
</Text>
</TouchableOpacity>
{!hideUnitChangeButton && !locked && (
<TouchableOpacity
onPress={() => this.onChangeUnits()}
style={{ marginLeft: 15 }}
>
{UnitsStore!.getNextUnit() === 'fiat' ? (
<ExchangeFiatSVG
fill={themeColor('text')}
width="35"
height="35"
/>
) : (
<ExchangeBitcoinSVG
fill={themeColor('text')}
width="35"
height="35"
/>
)}
</TouchableOpacity>
)}
</Row>
{!hideConversion && (
<View style={{ marginBottom: 10 }}>
{fiatEnabled && (
<Text
style={{
fontFamily: 'PPNeueMontreal-Book',
color: themeColor('text')
}}
>
{getRate(effectiveUnits === 'sats')}
</Text>
)}
{fiatEnabled && effectiveUnits !== 'fiat' && (
<Amount sats={satAmount} fixedUnits="fiat" />
)}
{effectiveUnits !== 'BTC' && (
<Amount sats={satAmount} fixedUnits="BTC" />
)}
{effectiveUnits !== 'sats' && (
<Amount sats={satAmount} fixedUnits="sats" />
)}
</View>
)}
</React.Fragment>
);
}
}
export { getSatAmount };
const styles = StyleSheet.create({
amountDisplay: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
height: 60,
top: 10,
borderRadius: 6,
marginBottom: 20,
paddingLeft: 10,
paddingRight: 10,
overflow: 'hidden'
},
amountText: {
fontFamily: 'PPNeueMontreal-Book',
fontSize: 20
}
});