diff --git a/app/src/androidTest/java/dev/shorthouse/coinwatch/ui/screen/DetailsScreenTest.kt b/app/src/androidTest/java/dev/shorthouse/coinwatch/ui/screen/DetailsScreenTest.kt index b4cb3a0e..9fc61476 100644 --- a/app/src/androidTest/java/dev/shorthouse/coinwatch/ui/screen/DetailsScreenTest.kt +++ b/app/src/androidTest/java/dev/shorthouse/coinwatch/ui/screen/DetailsScreenTest.kt @@ -169,9 +169,9 @@ class DetailsScreenTest { onNodeWithText("Market Cap Rank").assertIsDisplayed() onNodeWithText("2").assertIsDisplayed() onNodeWithText("Market Cap").assertIsDisplayed() - onNodeWithText("$225,722,901,094.00").assertIsDisplayed() + onNodeWithText("$225.72B").assertIsDisplayed() onNodeWithText("Volume (24h)").assertIsDisplayed() - onNodeWithText("$6,627,669,115.00").assertIsDisplayed() + onNodeWithText("$6.63B").assertIsDisplayed() onNodeWithText("Circulating Supply").assertIsDisplayed() onNodeWithText("120,186,525").assertIsDisplayed() onNodeWithText("All Time High").assertIsDisplayed() @@ -518,9 +518,9 @@ class DetailsScreenTest { onNodeWithText("Market Cap Rank").assertIsDisplayed() onNodeWithText("2").assertIsDisplayed() onNodeWithText("Market Cap").assertIsDisplayed() - onNodeWithText("£225,722,901,094.00").assertIsDisplayed() + onNodeWithText("£225.72B").assertIsDisplayed() onNodeWithText("Volume (24h)").assertIsDisplayed() - onNodeWithText("$6,627,669,115.00").assertIsDisplayed() + onNodeWithText("$6.63B").assertIsDisplayed() onNodeWithText("Circulating Supply").assertIsDisplayed() onNodeWithText("120,186,525").assertIsDisplayed() onNodeWithText("All Time High ($)").assertIsDisplayed() @@ -531,4 +531,93 @@ class DetailsScreenTest { onNodeWithText("7 Aug 2015").assertIsDisplayed() } } + + @Test + fun when_pricesAreLarge_should_showShortenedPrices() { + val uiStateSuccess = DetailsUiState.Success( + CoinDetails( + id = "ethereum", + name = "Ethereum", + symbol = "ETH", + imageUrl = "https://cdn.coinranking.com/rk4RKHOuW/eth.svg", + currentPrice = Price("1879.14"), + marketCap = Price("49491394.23440234"), + marketCapRank = "2", + volume24h = Price("1009900243"), + circulatingSupply = "120,186,525", + allTimeHigh = Price("3084938574102"), + allTimeHighDate = "10 Nov 2021", + listedDate = "7 Aug 2015" + ), + CoinChart( + prices = persistentListOf( + BigDecimal("1755.19"), + BigDecimal("1749.71"), + BigDecimal("1750.94"), + BigDecimal("1748.44"), + BigDecimal("1743.98"), + BigDecimal("1740.25") + ), + minPrice = Price("1632.46"), + maxPrice = Price("1922.83"), + periodPriceChangePercentage = Percentage("7.06") + ), + chartPeriod = ChartPeriod.Day, + isCoinFavourite = true + ) + + composeTestRule.setContent { + AppTheme { + DetailsScreen( + uiState = uiStateSuccess, + onNavigateUp = {}, + onClickFavouriteCoin = {}, + onClickChartPeriod = {} + ) + } + } + + composeTestRule.apply { + onNodeWithContentDescription("Back").assertIsDisplayed() + onNodeWithContentDescription("Favourite").assertIsDisplayed() + + onNodeWithText("Ethereum").assertIsDisplayed() + onNodeWithText("ETH").assertIsDisplayed() + onNodeWithText("$1,879.14").assertIsDisplayed() + onNodeWithText("+7.06%").assertIsDisplayed() + onNodeWithText("Past day").assertIsDisplayed() + + onNodeWithText("1H").assertIsDisplayed() + onNodeWithText("1D").assertIsDisplayed() + onNodeWithText("1W").assertIsDisplayed() + onNodeWithText("1M").assertIsDisplayed() + onNodeWithText("3M").assertIsDisplayed() + onNodeWithText("1Y").assertIsDisplayed() + onNodeWithText("5Y").assertIsDisplayed() + + onNodeWithText("Chart Range").assertIsDisplayed() + onNodeWithText("Low").assertIsDisplayed() + onNodeWithText("$1,632.46").assertIsDisplayed() + onNodeWithText("High").assertIsDisplayed() + onNodeWithText("$1,922.83").assertIsDisplayed() + + onNodeWithText("7 Aug 2015").performScrollTo() + + onNodeWithText("Market Stats").assertIsDisplayed() + onNodeWithText("Market Cap Rank").assertIsDisplayed() + onNodeWithText("2").assertIsDisplayed() + onNodeWithText("Market Cap").assertIsDisplayed() + onNodeWithText("$49.49M").assertIsDisplayed() + onNodeWithText("Volume (24h)").assertIsDisplayed() + onNodeWithText("$1.01B").assertIsDisplayed() + onNodeWithText("Circulating Supply").assertIsDisplayed() + onNodeWithText("120,186,525").assertIsDisplayed() + onNodeWithText("All Time High").assertIsDisplayed() + onNodeWithText("$3.08T").assertIsDisplayed() + onNodeWithText("All Time High Date").assertIsDisplayed() + onNodeWithText("10 Nov 2021").assertIsDisplayed() + onNodeWithText("Listed Date").assertIsDisplayed() + onNodeWithText("7 Aug 2015").assertIsDisplayed() + } + } } diff --git a/app/src/main/java/dev/shorthouse/coinwatch/model/Price.kt b/app/src/main/java/dev/shorthouse/coinwatch/model/Price.kt index 96e13f87..3c09aaca 100644 --- a/app/src/main/java/dev/shorthouse/coinwatch/model/Price.kt +++ b/app/src/main/java/dev/shorthouse/coinwatch/model/Price.kt @@ -3,45 +3,74 @@ package dev.shorthouse.coinwatch.model import dev.shorthouse.coinwatch.common.toSanitisedBigDecimalOrZero import dev.shorthouse.coinwatch.data.preferences.global.Currency import java.math.BigDecimal +import java.math.MathContext +import java.math.RoundingMode import java.text.DecimalFormat -import java.util.Currency as CurrencyCode import java.util.Locale +import java.util.Currency as CurrencyCode data class Price(val price: String?, val currency: Currency = Currency.USD) : Comparable { - companion object { - private val precisionThreshold = BigDecimal("-1.00")..BigDecimal("1.00") + val amount: BigDecimal = price.toSanitisedBigDecimalOrZero() - private fun createCurrencyFormat(decimalPlaces: Int, currency: Currency): DecimalFormat { - val currencyFormat = DecimalFormat.getCurrencyInstance(Locale.US) as DecimalFormat + private val currencyFormat by lazy { + val currencyFormat = DecimalFormat.getCurrencyInstance(Locale.US) as DecimalFormat - currencyFormat.minimumFractionDigits = decimalPlaces - currencyFormat.maximumFractionDigits = decimalPlaces + val decimalPlaces = if (amount in belowOneThreshold) 6 else 2 + val currencyCode = try { + CurrencyCode.getInstance(currency.name) + } catch (e: IllegalArgumentException) { + CurrencyCode.getInstance(Currency.USD.name) + } - val currencyCode = try { - CurrencyCode.getInstance(currency.name) - } catch (e: IllegalArgumentException) { - CurrencyCode.getInstance(Currency.USD.name) - } - currencyFormat.currency = currencyCode + currencyFormat.minimumFractionDigits = decimalPlaces + currencyFormat.maximumFractionDigits = decimalPlaces + currencyFormat.currency = currencyCode - return currencyFormat - } + currencyFormat } - val amount: BigDecimal = price.toSanitisedBigDecimalOrZero() + val formattedAmount: String = when { + price.isNullOrBlank() -> "${currency.symbol}--" + amount in belowOneThreshold || amount in smallThreshold -> currencyFormat.format(amount) + else -> { + val roundedAmount = amount.round(MathContext(5, RoundingMode.HALF_EVEN)) - private val currencyFormat = if (amount in precisionThreshold) { - createCurrencyFormat(decimalPlaces = 6, currency = currency) - } else { - createCurrencyFormat(decimalPlaces = 2, currency = currency) - } + val divisor = when (roundedAmount) { + in millionThreshold -> million + in billionThreshold -> billion + in trillionThreshold -> trillion + in quadrillionThreshold -> quadrillion + else -> BigDecimal.ONE + } + + val symbol = when (roundedAmount) { + in millionThreshold -> "M" + in billionThreshold -> "B" + in trillionThreshold -> "T" + in quadrillionThreshold -> "Q" + else -> "" + } + + val shortenedAmount = amount.divide(divisor, 2, RoundingMode.HALF_EVEN) - val formattedAmount: String = when (price) { - null -> "${currency.symbol} --" - else -> currencyFormat.format(amount) + currencyFormat.format(shortenedAmount) + symbol + } } - override fun compareTo(other: Price): Int { - return amount.compareTo(other.amount) + override fun compareTo(other: Price) = amount.compareTo(other.amount) + + companion object { + private val million = BigDecimal("1000000") + private val billion = BigDecimal("1000000000") + private val trillion = BigDecimal("1000000000000") + private val quadrillion = BigDecimal("1000000000000000") + private val quintillion = BigDecimal("1000000000000000000") + + private val belowOneThreshold = BigDecimal("-1.00")..BigDecimal("1.00") + private val smallThreshold = BigDecimal("1.00")..