Skip to content

Commit

Permalink
Closes #119; Localize input formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
Iliyan Germanov committed Nov 15, 2021
1 parent 70bffc3 commit 56f5af3
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 144 deletions.
154 changes: 154 additions & 0 deletions app/src/main/java/com/ivy/wallet/base/AmountFormatting.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.ivy.wallet.base

import com.ivy.wallet.model.IvyCurrency
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import kotlin.math.abs
import kotlin.math.log10

const val MILLION = 1000000
const val N_100K = 100000
const val THOUSAND = 1000

fun String.amountToDoubleOrNull(): Double? {
return this.normalizeAmount().toDoubleOrNull()
}

fun String.amountToDouble(): Double {
return this.normalizeAmount().toDouble()
}

fun String.normalizeAmount(): String {
return this.removeGroupingSeparator()
.normalizeDecimalSeparator()
}

fun String.normalizeExpression(): String {
return this.removeGroupingSeparator()
.normalizeDecimalSeparator()
}

fun String.removeGroupingSeparator(): String {
return replace(localGroupingSeparator(), "")
}

fun String.normalizeDecimalSeparator(): String {
return replace(localDecimalSeparator(), ".")
}

fun localDecimalSeparator(): String {
return DecimalFormatSymbols.getInstance().decimalSeparator.toString()
}

fun localGroupingSeparator(): String {
return DecimalFormatSymbols.getInstance().groupingSeparator.toString()
}

//Display Formatting
fun Double.format(digits: Int) = "%.${digits}f".format(this)

fun Double.format(currencyCode: String): String {
return this.format(IvyCurrency.fromCode(currencyCode))
}

fun Double.format(currency: IvyCurrency?): String {
return if (currency?.isCrypto == true) {
val result = this.formatCrypto()
return when {
result.lastOrNull() == localDecimalSeparator().firstOrNull() -> {
val newResult = result.dropLast(1)
if (newResult.isEmpty()) "0" else newResult
}
result.isEmpty() -> {
"0"
}
else -> result
}
} else {
formatFIAT()
}
}

fun Double.formatCrypto(): String {
val pattern = "###,###,##0.${"0".repeat(9)}"
val format = DecimalFormat(pattern)
val numberStringWithZeros = format.format(this)

var lastTrailingZeroIndex: Int? = null
for (i in numberStringWithZeros.lastIndex.downTo(0)) {
if (numberStringWithZeros[i] == '0') {
lastTrailingZeroIndex = i
} else {
break
}
}

return if (lastTrailingZeroIndex != null)
numberStringWithZeros.substring(0, lastTrailingZeroIndex) else numberStringWithZeros
}

private fun Double.formatFIAT(): String = DecimalFormat("#,##0.00").format(this)

fun shortenAmount(amount: Double): String {
return when {
abs(amount) >= MILLION -> {
formatShortenedNumber(amount / MILLION, "m")
}
abs(amount) >= THOUSAND -> {
formatShortenedNumber(amount / THOUSAND, "k")
}
else -> amount.toString()
}
}

private fun formatShortenedNumber(
number: Double,
extension: String
): String {
return if (hasSignificantDecimalPart(number)) {
"${number.format(2)}$extension"
} else {
"${number.toInt()}$extension"
}
}

fun hasSignificantDecimalPart(number: Double): Boolean {
//TODO: Review, might cause trouble when integrating crypto
val intPart = number.toInt()
return abs(number - intPart) >= 0.009
}

fun shouldShortAmount(amount: Double): Boolean {
return abs(amount) >= N_100K
}

fun formatInt(number: Int): String {
return DecimalFormat("#,###,###,###").format(number)
}

fun decimalPartFormatted(currency: String, value: Double): String {
return if (IvyCurrency.fromCode(currency)?.isCrypto == true) {
val decimalPartFormatted = value.formatCrypto()
.split(localDecimalSeparator())
.getOrNull(1) ?: "null"
if (decimalPartFormatted.isNotBlank())
"${localDecimalSeparator()}$decimalPartFormatted" else ""
} else {
"${localDecimalSeparator()}${decimalPartFormattedFIAT(value)}"
}
}

private fun decimalPartFormattedFIAT(value: Double): String {
return DecimalFormat(".00").format(value)
.split(localDecimalSeparator())
.getOrNull(1)
?: value.toString()
.split(localDecimalSeparator())
.getOrNull(1)
?: "null"
}

fun Long.length() = when (this) {
0L -> 1
else -> log10(abs(toDouble())).toInt() + 1
}
108 changes: 0 additions & 108 deletions app/src/main/java/com/ivy/wallet/base/UtilExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,10 @@ package com.ivy.wallet.base
import android.app.KeyguardManager
import android.content.Context
import android.icu.util.Currency
import com.ivy.wallet.model.IvyCurrency
import java.text.DecimalFormat
import java.util.*
import kotlin.math.abs
import kotlin.math.log10

const val MILLION = 1000000
const val N_100K = 100000
const val THOUSAND = 1000

fun ByteArray.toBase64(): String = Base64.getEncoder().encodeToString(this)

fun String.toBase64(): String = Base64.getEncoder().encodeToString(this.toByteArray())
Expand Down Expand Up @@ -70,76 +64,11 @@ fun <T> MutableList<T>?.orEmpty(): MutableList<T> {

fun String.nullifyEmpty() = if (this.isBlank()) null else this

fun Double.format(digits: Int) = "%.${digits}f".format(this)

fun Double.format(currencyCode: String): String {
return this.format(IvyCurrency.fromCode(currencyCode))
}

fun Double.format(currency: IvyCurrency?): String {
return if (currency?.isCrypto == true) {
val result = this.formatCrypto()
return when {
result.lastOrNull() == '.' -> {
val newResult = result.dropLast(1)
if (newResult.isEmpty()) "0" else newResult
}
result.isEmpty() -> {
"0"
}
else -> result
}
} else {
formatFIAT()
}
}

fun Double.formatCrypto(): String {
val pattern = "###,###,##0.${"0".repeat(9)}"
val format = DecimalFormat(pattern)
val numberStringWithZeros = format.format(this)

var lastTrailingZeroIndex: Int? = null
for (i in numberStringWithZeros.lastIndex.downTo(0)) {
if (numberStringWithZeros[i] == '0') {
lastTrailingZeroIndex = i
} else {
break
}
}

return if (lastTrailingZeroIndex != null)
numberStringWithZeros.substring(0, lastTrailingZeroIndex) else numberStringWithZeros
}

fun Long.length() = when (this) {
0L -> 1
else -> log10(abs(toDouble())).toInt() + 1
}

private fun Double.formatFIAT(): String = DecimalFormat("#,##0.00").format(this)

fun getDefaultFIATCurrency(): Currency =
Currency.getInstance(Locale.getDefault()) ?: Currency.getInstance("USD")
?: Currency.getInstance("usd") ?: Currency.getAvailableCurrencies().firstOrNull()
?: Currency.getInstance("EUR")

fun decimalPartFormatted(currency: String, value: Double): String {
return if (IvyCurrency.fromCode(currency)?.isCrypto == true) {
val decimalPartFormatted = value.formatCrypto()
.split(".")
.getOrNull(1) ?: "null"
if (decimalPartFormatted.isNotBlank()) ".$decimalPartFormatted" else ""
} else {
".${decimalPartFormattedFIAT(value)}"
}
}

private fun decimalPartFormattedFIAT(value: Double): String {
return DecimalFormat(".00").format(value).split(".", ",").getOrNull(1)
?: value.toString().split(".", ",").getOrNull(1) ?: "null"
}

fun String.toUpperCaseLocal() = this.toUpperCase(Locale.getDefault())

fun String.toLowerCaseLocal() = this.toLowerCase(Locale.getDefault())
Expand All @@ -148,43 +77,6 @@ fun String.uppercaseLocal(): String = this.toUpperCase(Locale.getDefault())

fun String.capitalizeLocal(): String = this.capitalize(Locale.getDefault())

fun shortenAmount(amount: Double): String {
return when {
abs(amount) >= MILLION -> {
formatShortenedNumber(amount / MILLION, "m")
}
abs(amount) >= THOUSAND -> {
formatShortenedNumber(amount / THOUSAND, "k")
}
else -> amount.toString()
}
}

private fun formatShortenedNumber(
number: Double,
extension: String
): String {
return if (hasSignificantDecimalPart(number)) {
"${number.format(2)}$extension"
} else {
"${number.toInt()}$extension"
}
}

fun hasSignificantDecimalPart(number: Double): Boolean {
//TODO: Review, might cause trouble when integrating crypto
val intPart = number.toInt()
return abs(number - intPart) >= 0.009
}

fun shouldShortAmount(amount: Double): Boolean {
return abs(amount) >= N_100K
}

fun formatInt(number: Int): String {
return DecimalFormat("#,###,###,###").format(number)
}

fun hasLockScreen(context: Context): Boolean {
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
return keyguardManager.isDeviceSecure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import com.ivy.wallet.ui.theme.components.IvyIcon
import com.ivy.wallet.ui.theme.modal.IvyModal
import com.ivy.wallet.ui.theme.modal.ModalPositiveButton
import com.ivy.wallet.ui.theme.modal.modalPreviewActionRowHeight
import java.text.DecimalFormatSymbols
import java.util.*
import kotlin.math.truncate

Expand Down Expand Up @@ -449,35 +448,6 @@ private fun circleButtonModifier(
.border(2.dp, IvyTheme.colors.medium, Shapes.roundedFull)
}

fun String.amountToDoubleOrNull(): Double? {
return this.normalizeAmount().toDoubleOrNull()
}

fun String.amountToDouble(): Double {
return this.normalizeAmount().toDouble()
}

fun String.normalizeAmount(): String {
return this.removeGroupingSeparator()
.normalizeDecimalSeparator()
}

fun String.removeGroupingSeparator(): String {
return replace(localGroupingSeparator(), "")
}

fun String.normalizeDecimalSeparator(): String {
return replace(localDecimalSeparator(), ".")
}

fun localDecimalSeparator(): String {
return DecimalFormatSymbols.getInstance().decimalSeparator.toString()
}

fun localGroupingSeparator(): String {
return DecimalFormatSymbols.getInstance().groupingSeparator.toString()
}

@Preview
@Composable
private fun Preview() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.ivy.wallet.base.format
import com.ivy.wallet.base.localDecimalSeparator
import com.ivy.wallet.base.normalizeExpression
import com.ivy.wallet.ui.IvyAppPreview
import com.ivy.wallet.ui.theme.*
import com.ivy.wallet.ui.theme.modal.IvyModal
Expand Down Expand Up @@ -117,7 +119,7 @@ fun BoxWithConstraintsScope.CalculatorModal(
KeypadCircleButton(text = "=") {
val result = calculate(expression)
if (result != null) {
expression = result.format(currency).removeGroupingSeparator()
expression = result.format(currency)
}
}
},
Expand Down Expand Up @@ -147,11 +149,6 @@ private fun calculate(expression: String): Double? {
}
}

private fun String.normalizeExpression(): String {
return this.replace(localGroupingSeparator(), "")
.replace(localDecimalSeparator(), ".")
}

@Preview
@Composable
private fun Preview() {
Expand Down

0 comments on commit 56f5af3

Please sign in to comment.