diff --git a/components/Amount.tsx b/components/Amount.tsx index 5dfc49b74..e184bd76d 100644 --- a/components/Amount.tsx +++ b/components/Amount.tsx @@ -1,18 +1,23 @@ import * as React from 'react'; import { TouchableOpacity, StyleSheet, View } from 'react-native'; import { inject, observer } from 'mobx-react'; + import FiatStore from '../stores/FiatStore'; import UnitsStore from '../stores/UnitsStore'; import SettingsStore from '../stores/SettingsStore'; -import PrivacyUtils from '../utils/PrivacyUtils'; -import ClockIcon from '../assets/images/SVG/Clock.svg'; -import { localeString } from '../utils/LocaleUtils'; -import { themeColor } from '../utils/ThemeUtils'; + import { Spacer } from './layout/Spacer'; import { Row } from './layout/Row'; import { Body } from './text/Body'; import LoadingIndicator from './LoadingIndicator'; +import { localeString } from '../utils/LocaleUtils'; +import { themeColor } from '../utils/ThemeUtils'; +import { formatBitcoinWithSpaces } from '../utils/UnitsUtils'; +import PrivacyUtils from '../utils/PrivacyUtils'; + +import ClockIcon from '../assets/images/SVG/Clock.svg'; + import stores from '../stores/Stores'; type Units = 'sats' | 'BTC' | 'fiat'; @@ -185,6 +190,8 @@ function AmountDisplay({ {negative ? '-' : ''} {amount === 'N/A' && fiatRatesLoading ? ( + ) : unit === 'BTC' ? ( + formatBitcoinWithSpaces(amount) ) : ( amount.toString() )} @@ -216,6 +223,8 @@ function AmountDisplay({ {negative ? '-' : ''} {amount === 'N/A' && fiatRatesLoading ? ( + ) : unit === 'BTC' ? ( + formatBitcoinWithSpaces(amount) ) : ( amount.toString() )} diff --git a/components/AmountInput.tsx b/components/AmountInput.tsx index b4f50cbbd..42fd28740 100644 --- a/components/AmountInput.tsx +++ b/components/AmountInput.tsx @@ -7,11 +7,12 @@ import Amount from './Amount'; import TextInput from './TextInput'; import { themeColor } from '../utils/ThemeUtils'; +import { SATS_PER_BTC } from '../utils/UnitsUtils'; import Stores from '../stores/Stores'; import FiatStore from '../stores/FiatStore'; import SettingsStore from '../stores/SettingsStore'; -import UnitsStore, { SATS_PER_BTC } from '../stores/UnitsStore'; +import UnitsStore from '../stores/UnitsStore'; import ExchangeBitcoinSVG from '../assets/images/SVG/ExchangeBitcoin.svg'; import ExchangeFiatSVG from '../assets/images/SVG/ExchangeFiat.svg'; diff --git a/components/LSPS1OrderResponse.tsx b/components/LSPS1OrderResponse.tsx index 2ca1ff8b3..46a8b0830 100644 --- a/components/LSPS1OrderResponse.tsx +++ b/components/LSPS1OrderResponse.tsx @@ -11,11 +11,11 @@ import Button from './Button'; import { localeString } from '../utils/LocaleUtils'; import { themeColor } from '../utils/ThemeUtils'; +import { numberWithCommas } from '../utils/UnitsUtils'; import UrlUtils from '../utils/UrlUtils'; import InvoicesStore from '../stores/InvoicesStore'; import NodeInfoStore from '../stores/NodeInfoStore'; -import FiatStore from '../stores/FiatStore'; import { ChannelItem } from './Channels/ChannelItem'; interface LSPS1OrderResponseProps { @@ -23,11 +23,10 @@ interface LSPS1OrderResponseProps { orderResponse: any; InvoicesStore?: InvoicesStore; NodeInfoStore?: NodeInfoStore; - FiatStore?: FiatStore; orderView: boolean; } -@inject('InvoicesStore', 'NodeInfoStore', 'FiatStore') +@inject('InvoicesStore', 'NodeInfoStore') @observer export default class LSPS1OrderResponse extends React.Component< LSPS1OrderResponseProps, @@ -38,7 +37,6 @@ export default class LSPS1OrderResponse extends React.Component< orderResponse, InvoicesStore, NodeInfoStore, - FiatStore, orderView, navigation } = this.props; @@ -108,7 +106,7 @@ export default class LSPS1OrderResponse extends React.Component< keyValue={localeString( 'views.LSPS1.channelExpiryBlocks' )} - value={FiatStore!.numberWithCommas( + value={numberWithCommas( orderResponse?.channel_expiry_blocks )} /> diff --git a/locales/en.json b/locales/en.json index 6d9a7d601..f47e0ac48 100644 --- a/locales/en.json +++ b/locales/en.json @@ -742,6 +742,7 @@ "views.Settings.Display.displayNickname": "Display node nickname on main views", "views.Settings.Display.bigKeypadButtons": "Big keypad buttons", "views.Settings.Display.showAllDecimalPlaces": "Show all decimal places", + "views.Settings.Display.removeDecimalSpaces": "Remove decimal spaces from Bitcoin denominated amounts", "views.Settings.Display.showMillisatoshiAmounts": "Show millisatoshi amounts", "views.Settings.Display.selectNodeOnStartup": "Select node on startup", "views.Settings.privacy": "Privacy", diff --git a/stores/FiatStore.ts b/stores/FiatStore.ts index 2efa51e66..b144a987a 100644 --- a/stores/FiatStore.ts +++ b/stores/FiatStore.ts @@ -3,7 +3,11 @@ import ReactNativeBlobUtil from 'react-native-blob-util'; import BigNumber from 'bignumber.js'; import SettingsStore from './SettingsStore'; -import { SATS_PER_BTC } from './UnitsStore'; +import { + SATS_PER_BTC, + numberWithCommas, + numberWithDecimals +} from '../utils/UnitsUtils'; interface CurrencyDisplayRules { symbol: string; @@ -24,13 +28,6 @@ export default class FiatStore { @observable public loading = false; @observable public error = false; - @observable public numberWithCommas = (x: string | number) => - x?.toString()?.replace(/\B(?=(\d{3})+(?!\d))/g, ',') || '0'; - - @observable public numberWithDecimals = (x: string | number) => - this.numberWithCommas(x).replace(/[,.]/g, (y: string) => - y === ',' ? '.' : ',' - ); private sourceOfCurrentFiatRates: string | undefined; getFiatRatesToken: any; @@ -581,12 +578,12 @@ export default class FiatStore { .toFixed(0); const formattedRate = separatorSwap - ? this.numberWithDecimals(rate) - : this.numberWithCommas(rate); + ? numberWithDecimals(rate) + : numberWithCommas(rate); const formattedMoscow = separatorSwap - ? this.numberWithDecimals(moscowTime) - : this.numberWithCommas(moscowTime); + ? numberWithDecimals(moscowTime) + : numberWithCommas(moscowTime); if (sats) { return `${formattedMoscow} sats = 1 ${fiat}`; @@ -749,8 +746,8 @@ export default class FiatStore { public formatAmountForDisplay = (input: string | number) => { const { symbol, space, rtl, separatorSwap } = this.getSymbol(); const amount = separatorSwap - ? this.numberWithDecimals(input) - : this.numberWithCommas(input); + ? numberWithDecimals(input) + : numberWithCommas(input); if (rtl) return `${amount}${space ? ' ' : ''}${symbol}`; return `${symbol}${space ? ' ' : ''}${amount}`; diff --git a/stores/PosStore.ts b/stores/PosStore.ts index a45ade8db..e149f0431 100644 --- a/stores/PosStore.ts +++ b/stores/PosStore.ts @@ -2,12 +2,14 @@ import { action, observable } from 'mobx'; import EncryptedStorage from 'react-native-encrypted-storage'; import ReactNativeBlobUtil from 'react-native-blob-util'; import BigNumber from 'bignumber.js'; +import { v4 as uuidv4 } from 'uuid'; -import { SATS_PER_BTC } from './UnitsStore'; +import FiatStore from './FiatStore'; import SettingsStore, { PosEnabled } from './SettingsStore'; + import Order from '../models/Order'; -import { v4 as uuidv4 } from 'uuid'; -import FiatStore from './FiatStore'; + +import { SATS_PER_BTC } from '../utils/UnitsUtils'; export interface orderPaymentInfo { orderId: string; diff --git a/stores/SettingsStore.ts b/stores/SettingsStore.ts index 115f27cb7..11e7a1584 100644 --- a/stores/SettingsStore.ts +++ b/stores/SettingsStore.ts @@ -44,6 +44,7 @@ interface DisplaySettings { displayNickname?: boolean; bigKeypadButtons?: boolean; showAllDecimalPlaces?: boolean; + removeDecimalSpaces?: boolean; showMillisatoshiAmounts?: boolean; } @@ -1106,6 +1107,7 @@ export default class SettingsStore { displayNickname: false, bigKeypadButtons: false, showAllDecimalPlaces: false, + removeDecimalSpaces: false, showMillisatoshiAmounts: true }, pos: { diff --git a/stores/UnitsStore.ts b/stores/UnitsStore.ts index abc6a7ea5..5cfca5ec6 100644 --- a/stores/UnitsStore.ts +++ b/stores/UnitsStore.ts @@ -3,12 +3,15 @@ import EncryptedStorage from 'react-native-encrypted-storage'; import SettingsStore from './SettingsStore'; import FiatStore from './FiatStore'; -import FeeUtils from './../utils/FeeUtils'; -type Units = 'sats' | 'BTC' | 'fiat'; +import { + SATS_PER_BTC, + numberWithCommas, + numberWithDecimals +} from '../utils/UnitsUtils'; +import FeeUtils from '../utils/FeeUtils'; -// 100_000_000 -export const SATS_PER_BTC = 100000000; +type Units = 'sats' | 'BTC' | 'fiat'; const UNIT_KEY = 'zeus-units'; @@ -94,7 +97,7 @@ export default class UnitsStore { }; } else if (units === 'sats') { return { - amount: this.fiatStore.numberWithCommas(absValueSats), + amount: numberWithCommas(absValueSats), unit: 'sats', negative, plural: !(Number(value) === 1 || Number(value) === -1) @@ -134,8 +137,8 @@ export default class UnitsStore { return { amount: separatorSwap - ? this.fiatStore.numberWithDecimals(amount) - : this.fiatStore.numberWithCommas(amount), + ? numberWithDecimals(amount) + : numberWithCommas(amount), unit: 'fiat', symbol, negative, @@ -177,9 +180,9 @@ export default class UnitsStore { Number(wholeSats || 0) / SATS_PER_BTC )}`; } else if (units === 'sats') { - const sats = `${ - this.fiatStore.numberWithCommas(wholeSats || value) || 0 - } ${Number(value) === 1 || Number(value) === -1 ? 'sat' : 'sats'}`; + const sats = `${numberWithCommas(wholeSats || value) || 0} ${ + Number(value) === 1 || Number(value) === -1 ? 'sat' : 'sats' + }`; return sats; } else if (units === 'fiat' && fiat) { if (this.fiatStore.fiatRates) { @@ -197,8 +200,8 @@ export default class UnitsStore { ).toFixed(2); const formattedAmount = separatorSwap - ? this.fiatStore.numberWithDecimals(amount) - : this.fiatStore.numberWithCommas(amount); + ? numberWithDecimals(amount) + : numberWithCommas(amount); if (rtl) { return `${formattedAmount}${space ? ' ' : ''}${symbol}`; @@ -231,9 +234,9 @@ export default class UnitsStore { return `₿${FeeUtils.toFixed(Number(value || 0))}`; } else if (units === 'sats') { const [wholeSats] = value.toString().split('.'); - const sats = `${ - this.fiatStore.numberWithCommas(wholeSats || value) || 0 - } ${Number(value) === 1 || Number(value) === -1 ? 'sat' : 'sats'}`; + const sats = `${numberWithCommas(wholeSats || value) || 0} ${ + Number(value) === 1 || Number(value) === -1 ? 'sat' : 'sats' + }`; return sats; } else if (units === 'fiat' && fiat) { if (this.fiatStore.fiatRates) { @@ -250,8 +253,8 @@ export default class UnitsStore { ).toFixed(2); const formattedAmount = separatorSwap - ? this.fiatStore.numberWithDecimals(amount) - : this.fiatStore.numberWithCommas(amount); + ? numberWithDecimals(amount) + : numberWithCommas(amount); if (rtl) { return `${formattedAmount}${space ? ' ' : ''}${symbol}`; diff --git a/utils/AddressUtils.test.ts b/utils/AddressUtils.test.ts index d7c706d11..d968bd574 100644 --- a/utils/AddressUtils.test.ts +++ b/utils/AddressUtils.test.ts @@ -5,6 +5,16 @@ jest.mock('react-native-encrypted-storage', () => ({ clear: jest.fn(() => Promise.resolve()) })); +jest.mock('../stores/Stores', () => ({ + SettingsStore: { + settings: { + display: { + removeDecimalSpaces: false + } + } + } +})); + import AddressUtils from './AddressUtils'; import { walletrpc } from '../proto/lightning'; diff --git a/utils/AddressUtils.ts b/utils/AddressUtils.ts index bb1460cf1..5882a2a22 100644 --- a/utils/AddressUtils.ts +++ b/utils/AddressUtils.ts @@ -3,7 +3,7 @@ const bitcoin = require('bitcoinjs-lib'); import Base64Utils from '../utils/Base64Utils'; -import { SATS_PER_BTC } from '../stores/UnitsStore'; +import { SATS_PER_BTC } from '../utils/UnitsUtils'; import { walletrpc } from '../proto/lightning'; diff --git a/utils/UnitsUtils.test.ts b/utils/UnitsUtils.test.ts index 7cf768c39..466baa2fa 100644 --- a/utils/UnitsUtils.test.ts +++ b/utils/UnitsUtils.test.ts @@ -1,14 +1,24 @@ +jest.mock('../stores/Stores', () => ({ + SettingsStore: { + settings: { + display: { + removeDecimalSpaces: false + } + } + } +})); + import { getDecimalPlaceholder } from './UnitsUtils'; describe('UnitsUtils', () => { describe('getDecimalPlaceholder', () => { it('Returns string and count properly', () => { expect(getDecimalPlaceholder('1231.2', 'BTC')).toEqual({ - string: '0000000', + string: '0 000 000', count: 7 }); expect(getDecimalPlaceholder('1231.', 'BTC')).toEqual({ - string: '00000000', + string: '00 000 000', count: 8 }); diff --git a/utils/UnitsUtils.ts b/utils/UnitsUtils.ts index b6a7bcfbc..a7f176216 100644 --- a/utils/UnitsUtils.ts +++ b/utils/UnitsUtils.ts @@ -1,6 +1,11 @@ +import stores from '../stores/Stores'; + +// 100_000_000 +const SATS_PER_BTC = 100000000; + const getDecimalPlaceholder = (amount: string, units: string) => { - const occupiedPlaces: number = - (amount.split('.')[1] && amount.split('.')[1].length) || 0; + const [_, decimalPart] = amount.split('.'); + const occupiedPlaces: number = (decimalPart && decimalPart.length) || 0; let placeholderCount = 0; if (units === 'sats') { @@ -12,9 +17,60 @@ const getDecimalPlaceholder = (amount: string, units: string) => { } return { - string: amount.includes('.') ? '0'.repeat(placeholderCount) : null, + string: amount.includes('.') + ? units === 'BTC' && + !stores?.settingsStore?.settings?.display?.removeDecimalSpaces + ? '00 000 000'.slice( + occupiedPlaces + + (decimalPart.length > 5 + ? 2 + : decimalPart.length > 2 + ? 1 + : 0) + ) + : '0'.repeat(placeholderCount) + : null, count: amount.includes('.') ? placeholderCount : 0 }; }; -export { getDecimalPlaceholder }; +const numberWithCommas = (x: string | number) => + x?.toString()?.replace(/\B(?=(\d{3})+(?!\d))/g, ',') || '0'; + +const numberWithDecimals = (x: string | number) => + numberWithCommas(x).replace(/[,.]/g, (y: string) => + y === ',' ? '.' : ',' + ); + +const formatBitcoinWithSpaces = (x: string | number) => { + // Convert to string to handle decimal parts + const [integerPart, decimalPart] = x.toString().split('.'); + + const integerFormatted = numberWithCommas(integerPart); + + // // If no decimal part, return the integer part as is + if (x.toString().includes('.') && !decimalPart) { + return `${integerFormatted}.`; + } else if (!decimalPart) { + return integerFormatted; + } + + if (stores?.settingsStore?.settings?.display?.removeDecimalSpaces) { + return `${integerFormatted}.${decimalPart}`; + } + + // Handle the first two characters, then group the rest in threes + const firstTwo = decimalPart.slice(0, 2); + const rest = decimalPart.slice(2).replace(/(\d{3})(?=\d)/g, '$1 '); + + // Combine integer part, first two characters, and formatted rest + return `${integerFormatted}.${firstTwo} ${rest}`.trim(); +}; + +export { + SATS_PER_BTC, + getDecimalPlaceholder, + numberWithCommas, + numberWithDecimals, + formatBitcoinWithSpaces +}; diff --git a/views/Activity/Activity.tsx b/views/Activity/Activity.tsx index 9cf5a6b58..9aa52d49c 100644 --- a/views/Activity/Activity.tsx +++ b/views/Activity/Activity.tsx @@ -23,13 +23,17 @@ import { Row } from '../../components/layout/Row'; import { localeString } from '../../utils/LocaleUtils'; import BackendUtils from '../../utils/BackendUtils'; import { themeColor } from '../../utils/ThemeUtils'; +import { + SATS_PER_BTC, + numberWithCommas, + numberWithDecimals +} from '../../utils/UnitsUtils'; import ActivityStore from '../../stores/ActivityStore'; import FiatStore from '../../stores/FiatStore'; import PosStore from '../../stores/PosStore'; import SettingsStore from '../../stores/SettingsStore'; import NotesStore from '../../stores/NotesStore'; -import { SATS_PER_BTC } from '../../stores/UnitsStore'; import Filter from '../../assets/images/SVG/Filter On.svg'; import Invoice from '../../models/Invoice'; @@ -200,8 +204,8 @@ export default class Activity extends React.PureComponent< }; const formattedRate = separatorSwap - ? FiatStore.numberWithDecimals(rate) - : FiatStore.numberWithCommas(rate); + ? numberWithDecimals(rate) + : numberWithCommas(rate); const exchangeRate = rtl ? `${formattedRate}${ diff --git a/views/Order.tsx b/views/Order.tsx index cf3af7b3f..538453785 100644 --- a/views/Order.tsx +++ b/views/Order.tsx @@ -23,10 +23,11 @@ import TextInput from '../components/TextInput'; import { localeString } from '../utils/LocaleUtils'; import { themeColor } from '../utils/ThemeUtils'; +import { SATS_PER_BTC } from '../utils/UnitsUtils'; import SettingsStore, { PosEnabled } from '../stores/SettingsStore'; import FiatStore from '../stores/FiatStore'; -import UnitsStore, { SATS_PER_BTC } from '../stores/UnitsStore'; +import UnitsStore from '../stores/UnitsStore'; import RNPrint from 'react-native-print'; import PosStore from '../stores/PosStore'; diff --git a/views/PendingHTLCs.tsx b/views/PendingHTLCs.tsx index e289e8981..03607acf0 100644 --- a/views/PendingHTLCs.tsx +++ b/views/PendingHTLCs.tsx @@ -15,15 +15,14 @@ import Switch from '../components/Switch'; import { localeString } from '../utils/LocaleUtils'; import { restartNeeded } from '../utils/RestartUtils'; import { themeColor } from '../utils/ThemeUtils'; +import { numberWithCommas } from '../utils/UnitsUtils'; import ChannelsStore from '../stores/ChannelsStore'; -import FiatStore from '../stores/FiatStore'; import SettingsStore from '../stores/SettingsStore'; interface PendingHTLCsProps { navigation: StackNavigationProp; ChannelsStore: ChannelsStore; - FiatStore: FiatStore; SettingsStore: SettingsStore; route: Route<'PendingHTLCs', { pending_htlcs: any }>; } @@ -35,7 +34,7 @@ interface PendingHTLCsState { const PERSISTENT_KEY = 'persistentServicesEnabled'; -@inject('ChannelsStore', 'FiatStore', 'SettingsStore') +@inject('ChannelsStore', 'SettingsStore') @observer export default class PendingHTLCs extends React.PureComponent< PendingHTLCsProps, @@ -71,8 +70,7 @@ export default class PendingHTLCs extends React.PureComponent< }; render() { - const { navigation, ChannelsStore, FiatStore, SettingsStore } = - this.props; + const { navigation, ChannelsStore, SettingsStore } = this.props; const { pendingHTLCs, persistentMode } = this.state; const { getChannels, loading } = ChannelsStore; const { updateSettings, implementation } = SettingsStore; @@ -103,9 +101,7 @@ export default class PendingHTLCs extends React.PureComponent< : localeString('views.PendingHTLCs.outgoing'); const subTitle = `${localeString( 'views.PendingHTLCs.expirationHeight' - )}: ${FiatStore.numberWithCommas( - item.expiration_height - )}`; + )}: ${numberWithCommas(item.expiration_height)}`; return ( diff --git a/views/Receive.tsx b/views/Receive.tsx index 3812035a7..28a2b5385 100644 --- a/views/Receive.tsx +++ b/views/Receive.tsx @@ -66,13 +66,14 @@ import PosStore from '../stores/PosStore'; import SettingsStore, { TIME_PERIOD_KEYS } from '../stores/SettingsStore'; import LightningAddressStore from '../stores/LightningAddressStore'; import LSPStore from '../stores/LSPStore'; -import UnitsStore, { SATS_PER_BTC } from '../stores/UnitsStore'; +import UnitsStore from '../stores/UnitsStore'; import { localeString } from '../utils/LocaleUtils'; import BackendUtils from '../utils/BackendUtils'; import Base64Utils from '../utils/Base64Utils'; import NFCUtils from '../utils/NFCUtils'; import { themeColor } from '../utils/ThemeUtils'; +import { SATS_PER_BTC } from '../utils/UnitsUtils'; import lndMobile from '../lndmobile/LndMobileInjection'; import { decodeSubscribeTransactionsResult } from '../lndmobile/onchain'; diff --git a/views/Settings/CurrencyConverter.tsx b/views/Settings/CurrencyConverter.tsx index 38b3820df..e510ac0ef 100644 --- a/views/Settings/CurrencyConverter.tsx +++ b/views/Settings/CurrencyConverter.tsx @@ -33,6 +33,8 @@ import Edit from '../../assets/images/SVG/Pen.svg'; import DragDots from '../../assets/images/SVG/DragDots.svg'; import BitcoinIcon from '../../assets/images/SVG/bitcoin-icon.svg'; +import { numberWithCommas } from '../../utils/UnitsUtils'; + interface CurrencyConverterProps { navigation: StackNavigationProp; FiatStore?: FiatStore; @@ -176,14 +178,14 @@ export default class CurrencyConverter extends React.Component< if (currency === 'BTC') { // If BTC and the value is greater than 1, apply formatting with numberWithCommas if (parseFloat(value) > 1) { - return FiatStore.numberWithCommas(value); + return numberWithCommas(value); } else { // Otherwise, return the value as it is return value; } } else { // For other currencies, apply formatting with numberWithCommas - return FiatStore.numberWithCommas(value); + return numberWithCommas(value); } }; diff --git a/views/Settings/Display.tsx b/views/Settings/Display.tsx index 8dfa8f0c3..6699dfdc6 100644 --- a/views/Settings/Display.tsx +++ b/views/Settings/Display.tsx @@ -28,6 +28,7 @@ interface DisplayState { displayNickname: boolean; bigKeypadButtons: boolean; showAllDecimalPlaces: boolean; + removeDecimalSpaces: boolean; showMillisatoshiAmounts: boolean; selectNodeOnStartup: boolean; } @@ -44,6 +45,7 @@ export default class Display extends React.Component< displayNickname: false, bigKeypadButtons: false, showAllDecimalPlaces: false, + removeDecimalSpaces: false, showMillisatoshiAmounts: false, selectNodeOnStartup: false }; @@ -65,6 +67,9 @@ export default class Display extends React.Component< showAllDecimalPlaces: (settings.display && settings.display.showAllDecimalPlaces) || false, + removeDecimalSpaces: + (settings.display && settings.display.removeDecimalSpaces) || + false, showMillisatoshiAmounts: (settings.display && settings.display.showMillisatoshiAmounts) || @@ -90,6 +95,7 @@ export default class Display extends React.Component< bigKeypadButtons, theme, showAllDecimalPlaces, + removeDecimalSpaces, showMillisatoshiAmounts, selectNodeOnStartup } = this.state; @@ -126,6 +132,7 @@ export default class Display extends React.Component< bigKeypadButtons, defaultView, showAllDecimalPlaces, + removeDecimalSpaces, showMillisatoshiAmounts } }); @@ -156,6 +163,7 @@ export default class Display extends React.Component< bigKeypadButtons, theme, showAllDecimalPlaces, + removeDecimalSpaces, showMillisatoshiAmounts } }); @@ -200,6 +208,7 @@ export default class Display extends React.Component< bigKeypadButtons, displayNickname: !displayNickname, showAllDecimalPlaces, + removeDecimalSpaces, showMillisatoshiAmounts } }); @@ -245,6 +254,7 @@ export default class Display extends React.Component< displayNickname, bigKeypadButtons: !bigKeypadButtons, showAllDecimalPlaces, + removeDecimalSpaces, showMillisatoshiAmounts } }); @@ -292,6 +302,55 @@ export default class Display extends React.Component< bigKeypadButtons, showAllDecimalPlaces: !showAllDecimalPlaces, + removeDecimalSpaces, + showMillisatoshiAmounts + } + }); + }} + /> + + + + + + {localeString( + 'views.Settings.Display.removeDecimalSpaces' + )} + + + { + this.setState({ + removeDecimalSpaces: + !removeDecimalSpaces + }); + await updateSettings({ + display: { + defaultView, + theme, + displayNickname, + bigKeypadButtons, + showAllDecimalPlaces, + removeDecimalSpaces: + !removeDecimalSpaces, showMillisatoshiAmounts } }); @@ -299,6 +358,7 @@ export default class Display extends React.Component< /> + ; } @@ -74,7 +73,6 @@ interface LSPS1State { 'ChannelsStore', 'InvoicesStore', 'SettingsStore', - 'FiatStore', 'NodeInfoStore' ) @observer @@ -352,7 +350,6 @@ export default class LSPS1 extends React.Component { navigation, LSPStore, InvoicesStore, - FiatStore, NodeInfoStore, SettingsStore } = this.props; @@ -683,9 +680,9 @@ export default class LSPS1 extends React.Component { keyValue={`${localeString( 'views.Channel.channelBalance' )}`} - value={`${FiatStore.numberWithCommas( + value={`${numberWithCommas( info?.min_channel_balance_sat - )} - ${FiatStore.numberWithCommas( + )} - ${numberWithCommas( info?.max_channel_balance_sat )} ${localeString( 'general.sats' @@ -698,9 +695,9 @@ export default class LSPS1 extends React.Component { keyValue={`${localeString( 'views.LSPS1.initialLSPBalance' )}`} - value={`${FiatStore.numberWithCommas( + value={`${numberWithCommas( info?.min_initial_lsp_balance_sat - )} - ${FiatStore.numberWithCommas( + )} - ${numberWithCommas( info?.max_initial_lsp_balance_sat )} ${localeString( 'general.sats' @@ -736,7 +733,7 @@ export default class LSPS1 extends React.Component { keyValue={localeString( 'views.LSPS1.maxChannelExpiryBlocks' )} - value={FiatStore.numberWithCommas( + value={numberWithCommas( info?.max_channel_expiry_blocks )} /> @@ -833,9 +830,7 @@ export default class LSPS1 extends React.Component { placeholder={`${localeString( 'views.LSPS1.initialLSPBalance' )} (${localeString('general.sats')})`} - value={FiatStore.numberWithCommas( - lspBalanceSat - )} + value={numberWithCommas(lspBalanceSat)} onChangeText={(text: any) => { const value = text.replace( /,/g, @@ -856,7 +851,7 @@ export default class LSPS1 extends React.Component { color: themeColor('text') }} > - {FiatStore.numberWithCommas( + {numberWithCommas( info?.min_initial_lsp_balance_sat )} @@ -865,7 +860,7 @@ export default class LSPS1 extends React.Component { color: themeColor('text') }} > - {FiatStore.numberWithCommas( + {numberWithCommas( info?.max_initial_lsp_balance_sat )} @@ -914,7 +909,7 @@ export default class LSPS1 extends React.Component { placeholder={localeString( 'views.LSPS1.channelExpiryBlocks' )} - value={FiatStore.numberWithCommas( + value={numberWithCommas( channelExpiryBlocks )} onChangeText={(text: any) => { @@ -1044,7 +1039,7 @@ export default class LSPS1 extends React.Component { )} (${localeString( 'general.sats' )})`} - value={FiatStore.numberWithCommas( + value={numberWithCommas( clientBalanceSat ).toString()} onChangeText={( @@ -1075,7 +1070,7 @@ export default class LSPS1 extends React.Component { ) }} > - {FiatStore.numberWithCommas( + {numberWithCommas( info?.min_initial_client_balance_sat )} @@ -1086,7 +1081,7 @@ export default class LSPS1 extends React.Component { ) }} > - {FiatStore.numberWithCommas( + {numberWithCommas( info?.max_initial_client_balance_sat )} diff --git a/views/Settings/PointOfSaleRecon.tsx b/views/Settings/PointOfSaleRecon.tsx index afc9da1fb..4023942c1 100644 --- a/views/Settings/PointOfSaleRecon.tsx +++ b/views/Settings/PointOfSaleRecon.tsx @@ -21,11 +21,11 @@ import Export from '../../assets/images/SVG/Export.svg'; import FiatStore from '../../stores/FiatStore'; import PosStore from '../../stores/PosStore'; -import { SATS_PER_BTC } from '../../stores/UnitsStore'; import BackendUtils from '../../utils/BackendUtils'; import { localeString } from '../../utils/LocaleUtils'; import { themeColor } from '../../utils/ThemeUtils'; +import { SATS_PER_BTC } from '../../utils/UnitsUtils'; import { ReconHeader } from './PointOfSaleReconHeader'; diff --git a/views/Wallet/KeypadPane.tsx b/views/Wallet/KeypadPane.tsx index 4e5e795ec..40d03c35e 100644 --- a/views/Wallet/KeypadPane.tsx +++ b/views/Wallet/KeypadPane.tsx @@ -12,7 +12,6 @@ import WalletHeader from '../../components/WalletHeader'; import { getSatAmount } from '../../components/AmountInput'; import ChannelsStore from '../../stores/ChannelsStore'; -import FiatStore from '../../stores/FiatStore'; import NodeInfoStore from '../../stores/NodeInfoStore'; import SettingsStore from '../../stores/SettingsStore'; import UnitsStore from '../../stores/UnitsStore'; @@ -20,12 +19,15 @@ import UnitsStore from '../../stores/UnitsStore'; import BackendUtils from '../../utils/BackendUtils'; import { localeString } from '../../utils/LocaleUtils'; import { themeColor } from '../../utils/ThemeUtils'; -import { getDecimalPlaceholder } from '../../utils/UnitsUtils'; +import { + getDecimalPlaceholder, + formatBitcoinWithSpaces, + numberWithCommas +} from '../../utils/UnitsUtils'; interface KeypadPaneProps { navigation: StackNavigationProp; ChannelsStore?: ChannelsStore; - FiatStore?: FiatStore; NodeInfoStore?: NodeInfoStore; SettingsStore?: SettingsStore; UnitsStore?: UnitsStore; @@ -39,15 +41,7 @@ interface KeypadPaneState { lspNotConfigured: boolean; } -const MAX_LENGTH = 10; - -@inject( - 'ChannelsStore', - 'FiatStore', - 'NodeInfoStore', - 'SettingsStore', - 'UnitsStore' -) +@inject('ChannelsStore', 'NodeInfoStore', 'SettingsStore', 'UnitsStore') @observer export default class KeypadPane extends React.PureComponent< KeypadPaneProps, @@ -99,17 +93,34 @@ export default class KeypadPane extends React.PureComponent< return this.startShake(); } if (units === 'BTC') { - if (amount.split('.')[1] && amount.split('.')[1].length == 8) + const [integerPart, decimalPart] = amount.split('.'); + // deny if trying to add more than 8 figures of Bitcoin + if ( + !decimalPart && + integerPart && + integerPart.length == 8 && + !amount.includes('.') && + value !== '.' + ) + return this.startShake(); + // deny if trying to add more than 8 decimal places of satoshis + if (decimalPart && decimalPart.length == 8) return this.startShake(); } - if (amount.length >= MAX_LENGTH) { - newAmount = amount; + const proposedNewAmountStr = `${amount}${value}`; + const proposedNewAmount = new BigNumber(proposedNewAmountStr); + + // deny if exceeding BTC 21 million capacity + if (units === 'BTC' && proposedNewAmount.gt(21000000)) return this.startShake(); - } else if (amount === '0') { + if (units === 'sats' && proposedNewAmount.gt(2100000000000000.0)) + return this.startShake(); + + if (amount === '0') { newAmount = value; } else { - newAmount = `${amount}${value}`; + newAmount = proposedNewAmountStr; } let needInbound = false; @@ -197,8 +208,10 @@ export default class KeypadPane extends React.PureComponent< return needInbound ? 40 : 50; case 8: return needInbound ? 35 : 45; - default: + case 9: return needInbound ? 25 : 35; + default: + return needInbound ? 20 : 30; } }; @@ -242,7 +255,7 @@ export default class KeypadPane extends React.PureComponent< }; render() { - const { FiatStore, UnitsStore, navigation } = this.props; + const { UnitsStore, navigation } = this.props; const { amount, needInbound, belowMinAmount, overrideBelowMinAmount } = this.state; const { units } = UnitsStore!; @@ -326,7 +339,9 @@ export default class KeypadPane extends React.PureComponent< fontFamily: 'PPNeueMontreal-Medium' }} > - {FiatStore?.numberWithCommas(amount)} + {units === 'BTC' + ? formatBitcoinWithSpaces(amount) + : numberWithCommas(amount)} {getDecimalPlaceholder(amount, units).string} diff --git a/views/Wallet/SquarePosPane.tsx b/views/Wallet/SquarePosPane.tsx index 4b0de0dae..64fc0eb13 100644 --- a/views/Wallet/SquarePosPane.tsx +++ b/views/Wallet/SquarePosPane.tsx @@ -25,12 +25,13 @@ import ActivityStore from '../../stores/ActivityStore'; import FiatStore from '../../stores/FiatStore'; import NodeInfoStore from '../../stores/NodeInfoStore'; import PosStore from '../../stores/PosStore'; -import UnitsStore, { SATS_PER_BTC } from '../../stores/UnitsStore'; +import UnitsStore from '../../stores/UnitsStore'; import SettingsStore from '../../stores/SettingsStore'; import { localeString } from '../../utils/LocaleUtils'; import { protectedNavigation } from '../../utils/NavigationUtils'; import { themeColor } from '../../utils/ThemeUtils'; +import { SATS_PER_BTC } from '../../utils/UnitsUtils'; import { version } from './../../package.json'; diff --git a/views/Wallet/StandalonePosKeypadPane.tsx b/views/Wallet/StandalonePosKeypadPane.tsx index 9820611ac..e33273b46 100644 --- a/views/Wallet/StandalonePosKeypadPane.tsx +++ b/views/Wallet/StandalonePosKeypadPane.tsx @@ -10,21 +10,23 @@ import UnitToggle from '../../components/UnitToggle'; import WalletHeader from '../../components/WalletHeader'; import ChannelsStore from '../../stores/ChannelsStore'; -import FiatStore from '../../stores/FiatStore'; -import UnitsStore, { SATS_PER_BTC } from '../../stores/UnitsStore'; +import UnitsStore from '../../stores/UnitsStore'; import SettingsStore from '../../stores/SettingsStore'; import PosStore from '../../stores/PosStore'; import { localeString } from '../../utils/LocaleUtils'; import { themeColor } from '../../utils/ThemeUtils'; -import { getDecimalPlaceholder } from '../../utils/UnitsUtils'; +import { + SATS_PER_BTC, + getDecimalPlaceholder, + numberWithCommas +} from '../../utils/UnitsUtils'; import { PricedIn } from '../../models/Product'; interface PosKeypadPaneProps { navigation: StackNavigationProp; ChannelsStore?: ChannelsStore; - FiatStore?: FiatStore; UnitsStore?: UnitsStore; SettingsStore?: SettingsStore; PosStore?: PosStore; @@ -36,7 +38,7 @@ interface PosKeypadPaneState { const MAX_LENGTH = 10; -@inject('ChannelsStore', 'FiatStore', 'UnitsStore', 'SettingsStore', 'PosStore') +@inject('ChannelsStore', 'UnitsStore', 'SettingsStore', 'PosStore') @observer export default class PosKeypadPane extends React.PureComponent< PosKeypadPaneProps, @@ -206,7 +208,7 @@ export default class PosKeypadPane extends React.PureComponent< }; render() { - const { FiatStore, UnitsStore, navigation } = this.props; + const { UnitsStore, navigation } = this.props; const { amount } = this.state; const { units } = UnitsStore!; @@ -241,7 +243,7 @@ export default class PosKeypadPane extends React.PureComponent< fontFamily: 'PPNeueMontreal-Medium' }} > - {FiatStore?.numberWithCommas(amount)} + {numberWithCommas(amount)} {getDecimalPlaceholder(amount, units).string} diff --git a/views/Wallet/StandalonePosPane.tsx b/views/Wallet/StandalonePosPane.tsx index 86b9429ed..b2e7863af 100644 --- a/views/Wallet/StandalonePosPane.tsx +++ b/views/Wallet/StandalonePosPane.tsx @@ -28,13 +28,14 @@ import ActivityStore from '../../stores/ActivityStore'; import FiatStore from '../../stores/FiatStore'; import NodeInfoStore from '../../stores/NodeInfoStore'; import PosStore from '../../stores/PosStore'; -import UnitsStore, { SATS_PER_BTC } from '../../stores/UnitsStore'; +import UnitsStore from '../../stores/UnitsStore'; import SettingsStore from '../../stores/SettingsStore'; import InventoryStore from '../../stores/InventoryStore'; import { localeString } from '../../utils/LocaleUtils'; import { protectedNavigation } from '../../utils/NavigationUtils'; import { themeColor } from '../../utils/ThemeUtils'; +import { SATS_PER_BTC } from '../../utils/UnitsUtils'; import { version } from './../../package.json';