diff --git a/README.md b/README.md
index de9d30f..715694d 100755
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
A library to dynamically format your `EditTexts` to take currency inputs.
-[![Build Status](https://travis-ci.com/CottaCush/CurrencyEditText.svg?branch=master)](https://travis-ci.com/CottaCush/CurrencyEditText)
-[ ![Download](https://api.bintray.com/packages/cottacush/maven/CurrencyEditText/images/download.svg) ](https://bintray.com/cottacush/maven/CurrencyEditText/_latestVersion)
+[![ci](https://github.com/CottaCush/CurrencyEditText/actions/workflows/ci.yml/badge.svg)](https://github.com/CottaCush/CurrencyEditText/actions/workflows/ci.yml)
+[![Maven Central](https://img.shields.io/maven-central/v/com.cottacush/CurrencyEditText.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.cottacush%22%20AND%20a:%22CurrencyEditText%22)
@@ -13,8 +13,9 @@ A library to dynamically format your `EditTexts` to take currency inputs.
Add the dependency to your app's `build.gradle`:
```groovy
-implementation 'com.cottacush:CurrencyEditText:0.0.7'
+implementation 'com.cottacush:CurrencyEditText:'
```
+For versions, kindly head over to the [releases page](https://github.com/CottaCush/CurrencyEditText/releases)
## Usage
@@ -66,6 +67,20 @@ The `CurrencyEditText` uses the default `Locale` if no locale is specified. `Loc
```kotlin
currencyEditText.setLocale("en-NG") //Requires API level 21 and above.
```
+
+### Decimal Places
+The maximum number of decimal digits can be specified using the `maxNumberOfDecimalDigits` attributes in the xml, requiring
+a minimum value of 1. It has a default value of 2.
+
+```xml
+
+```
+or programmatically:
+```kotlin
+ currencyEditText.setMaxNumberOfDecimalDigits(3)
+```
## Getting the input value
diff --git a/library/src/main/java/com/cottacush/android/currencyedittext/CurrencyEditText.kt b/library/src/main/java/com/cottacush/android/currencyedittext/CurrencyEditText.kt
index 0697cef..d48ca18 100644
--- a/library/src/main/java/com/cottacush/android/currencyedittext/CurrencyEditText.kt
+++ b/library/src/main/java/com/cottacush/android/currencyedittext/CurrencyEditText.kt
@@ -25,10 +25,14 @@ import com.google.android.material.textfield.TextInputEditText
import java.math.BigDecimal
import java.util.*
-class CurrencyEditText(context: Context, attrs: AttributeSet?) : TextInputEditText(context, attrs) {
+class CurrencyEditText(
+ context: Context,
+ attrs: AttributeSet?
+) : TextInputEditText(context, attrs) {
private lateinit var currencySymbolPrefix: String
private var textWatcher: CurrencyInputWatcher
private var locale: Locale = Locale.getDefault()
+ private var maxDP: Int
init {
var useCurrencySymbolAsHint = false
@@ -44,6 +48,7 @@ class CurrencyEditText(context: Context, attrs: AttributeSet?) : TextInputEditTe
prefix = getString(R.styleable.CurrencyEditText_currencySymbol).orEmpty()
localeTag = getString(R.styleable.CurrencyEditText_localeTag)
useCurrencySymbolAsHint = getBoolean(R.styleable.CurrencyEditText_useCurrencySymbolAsHint, false)
+ maxDP = getInt(R.styleable.CurrencyEditText_maxNumberOfDecimalDigits, 2)
} finally {
recycle()
}
@@ -51,7 +56,7 @@ class CurrencyEditText(context: Context, attrs: AttributeSet?) : TextInputEditTe
currencySymbolPrefix = if (prefix.isBlank()) "" else "$prefix "
if (useCurrencySymbolAsHint) hint = currencySymbolPrefix
if (isLollipopAndAbove() && !localeTag.isNullOrBlank()) locale = getLocaleFromTag(localeTag!!)
- textWatcher = CurrencyInputWatcher(this, currencySymbolPrefix, locale)
+ textWatcher = CurrencyInputWatcher(this, currencySymbolPrefix, locale, maxDP)
addTextChangedListener(textWatcher)
}
@@ -72,9 +77,14 @@ class CurrencyEditText(context: Context, attrs: AttributeSet?) : TextInputEditTe
invalidateTextWatcher()
}
+ fun setMaxNumberOfDecimalDigits(maxDP: Int) {
+ this.maxDP = maxDP
+ invalidateTextWatcher()
+ }
+
private fun invalidateTextWatcher() {
removeTextChangedListener(textWatcher)
- textWatcher = CurrencyInputWatcher(this, currencySymbolPrefix, locale)
+ textWatcher = CurrencyInputWatcher(this, currencySymbolPrefix, locale, maxDP)
addTextChangedListener(textWatcher)
}
diff --git a/library/src/main/java/com/cottacush/android/currencyedittext/CurrencyInputWatcher.kt b/library/src/main/java/com/cottacush/android/currencyedittext/CurrencyInputWatcher.kt
index 07aa015..2e8f156 100644
--- a/library/src/main/java/com/cottacush/android/currencyedittext/CurrencyInputWatcher.kt
+++ b/library/src/main/java/com/cottacush/android/currencyedittext/CurrencyInputWatcher.kt
@@ -19,6 +19,7 @@ import android.annotation.SuppressLint
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText
+import java.lang.IllegalArgumentException
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
@@ -30,11 +31,17 @@ import kotlin.math.min
class CurrencyInputWatcher(
private val editText: EditText,
private val currencySymbol: String,
- locale: Locale
+ locale: Locale,
+ private val maxNumberOfDecimalPlaces: Int = 2
) : TextWatcher {
+ init {
+ if (maxNumberOfDecimalPlaces < 1) {
+ throw IllegalArgumentException("Maximum number of Decimal Digits must be a positive integer")
+ }
+ }
+
companion object {
- const val MAX_NO_OF_DECIMAL_PLACES = 2
const val FRACTION_FORMAT_PATTERN_PREFIX = "#,##0."
}
@@ -124,6 +131,6 @@ class CurrencyInputWatcher(
*/
private fun getFormatSequenceAfterDecimalSeparator(number: String): String {
val noOfCharactersAfterDecimalPoint = number.length - number.indexOf(decimalFormatSymbols.decimalSeparator) - 1
- return "0".repeat(min(noOfCharactersAfterDecimalPoint, MAX_NO_OF_DECIMAL_PLACES))
+ return "0".repeat(min(noOfCharactersAfterDecimalPoint, maxNumberOfDecimalPlaces))
}
}
diff --git a/library/src/main/res-public/values/attrs.xml b/library/src/main/res-public/values/attrs.xml
index 36f781c..9ee26ef 100644
--- a/library/src/main/res-public/values/attrs.xml
+++ b/library/src/main/res-public/values/attrs.xml
@@ -4,5 +4,6 @@
+
\ No newline at end of file
diff --git a/library/src/test/java/com/cottacush/android/currencyedittext/CurrencyInputWatcherTest.kt b/library/src/test/java/com/cottacush/android/currencyedittext/CurrencyInputWatcherTest.kt
index deaeee0..b6523d3 100644
--- a/library/src/test/java/com/cottacush/android/currencyedittext/CurrencyInputWatcherTest.kt
+++ b/library/src/test/java/com/cottacush/android/currencyedittext/CurrencyInputWatcherTest.kt
@@ -17,8 +17,10 @@ package com.cottacush.android.currencyedittext
import android.text.Editable
import com.cottacush.android.currencyedittext.model.LocaleVars
+import org.junit.Assert
import org.junit.Test
import org.mockito.Mockito.*
+import java.lang.IllegalArgumentException
class CurrencyInputWatcherTest {
@@ -277,12 +279,132 @@ class CurrencyInputWatcherTest {
}
}
- private fun setupTestVariables(locale: LocaleVars): TestVars {
+ @Test
+ fun `Should keep a default of 2 decimal places when the max dp value isn't specified`() {
+ for (locale in locales) {
+ val currentEditTextContent = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}50992"
+ val expectedText = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}50"
+
+ val (editText, editable, watcher) = setupTestVariables(locale)
+ val watcherWithDefaultDP = CurrencyInputWatcher(editText, locale.currencySymbol, locale.tag.toLocale())
+ `when`(editable.toString()).thenReturn(currentEditTextContent)
+
+ watcherWithDefaultDP.runAllWatcherMethods(editable)
+
+ verify(editText, times(1)).setText(expectedText)
+ }
+ }
+
+ @Test
+ fun `Should throw an Exception when maximum dp is set to zero`() {
+ for (locale in locales) {
+ try {
+ val (editText, editable, watcher) = setupTestVariables(locale, decimalPlaces = 0)
+ Assert.fail("Should have caught an illegalArgumentException at this point")
+ } catch (e: IllegalArgumentException) { }
+ }
+ }
+
+ @Test
+ fun `Should keep only one decimal place when maximum dp is set to 1`() {
+ for (locale in locales) {
+ val currentEditTextContent = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}50992"
+ val expectedText = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}5"
+
+ val (editText, editable, watcher) = setupTestVariables(locale, decimalPlaces = 1)
+ `when`(editable.toString()).thenReturn(currentEditTextContent)
+
+ watcher.runAllWatcherMethods(editable)
+
+ verify(editText, times(1)).setText(expectedText)
+ }
+ }
+
+ @Test
+ fun `Should keep only two decimal places when maximum dp is set to 2`() {
+ for (locale in locales) {
+ val currentEditTextContent = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}51992"
+ val expectedText = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}51"
+
+ val (editText, editable, watcher) = setupTestVariables(locale, decimalPlaces = 2)
+ `when`(editable.toString()).thenReturn(currentEditTextContent)
+
+ watcher.runAllWatcherMethods(editable)
+
+ verify(editText, times(1)).setText(expectedText)
+ }
+ }
+
+ @Test
+ fun `Should keep only three decimal places when maximum dp is set to 3`() {
+ for (locale in locales) {
+ val currentEditTextContent = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}51992"
+ val expectedText = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}519"
+
+ val (editText, editable, watcher) = setupTestVariables(locale, decimalPlaces = 3)
+ `when`(editable.toString()).thenReturn(currentEditTextContent)
+
+ watcher.runAllWatcherMethods(editable)
+
+ verify(editText, times(1)).setText(expectedText)
+ }
+ }
+
+ @Test
+ fun `Should keep only seven decimal digits when maximum dp is set to 7`() {
+ for (locale in locales) {
+ val currentEditTextContent = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}519923345634"
+ val expectedText = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}5199233"
+
+ val (editText, editable, watcher) = setupTestVariables(locale, decimalPlaces = 7)
+ `when`(editable.toString()).thenReturn(currentEditTextContent)
+
+ watcher.runAllWatcherMethods(editable)
+
+ verify(editText, times(1)).setText(expectedText)
+ }
+ }
+
+ @Test
+ fun `Should keep up to ten decimal places when maximum dp is set to 10`() {
+ for (locale in locales) {
+ val currentEditTextContent = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}519923345634"
+ val expectedText = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}5199233456"
+
+ val (editText, editable, watcher) = setupTestVariables(locale, decimalPlaces = 10)
+ `when`(editable.toString()).thenReturn(currentEditTextContent)
+
+ watcher.runAllWatcherMethods(editable)
+
+ verify(editText, times(1)).setText(expectedText)
+ }
+ }
+
+ @Test
+ fun `Should change maximum decimal digits to 3 if setMaxNumberOfDecimalDigits(3) is called after being init with decimal digits 2`() {
+ for (locale in locales) {
+ val currentEditTextContent = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}519923345634"
+ val firstExpectedText = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}51"
+ val secondExpectedText = "${locale.currencySymbol}1${locale.groupingSeparator}320${locale.decimalSeparator}519"
+
+ val (editText, editable, watcher) = setupTestVariables(locale, decimalPlaces = 2)
+ val secondWatcher = locale.toWatcher(editText, 3)
+ `when`(editable.toString()).thenReturn(currentEditTextContent)
+
+ watcher.runAllWatcherMethods(editable)
+ secondWatcher.runAllWatcherMethods(editable)
+
+ verify(editText, times(1)).setText(firstExpectedText)
+ verify(editText, times(1)).setText(secondExpectedText)
+ }
+ }
+
+ private fun setupTestVariables(locale: LocaleVars, decimalPlaces: Int = 2): TestVars {
val editText = mock(CurrencyEditText::class.java)
val editable = mock(Editable::class.java)
`when`(editText.text).thenReturn(editable)
`when`(editable.append(isA(String::class.java))).thenReturn(editable)
- val watcher = locale.toWatcher(editText)
+ val watcher = locale.toWatcher(editText, decimalPlaces)
return TestVars(editText, editable, watcher)
}
}
diff --git a/library/src/test/java/com/cottacush/android/currencyedittext/Exts.kt b/library/src/test/java/com/cottacush/android/currencyedittext/Exts.kt
index a6d58de..7a35ea6 100644
--- a/library/src/test/java/com/cottacush/android/currencyedittext/Exts.kt
+++ b/library/src/test/java/com/cottacush/android/currencyedittext/Exts.kt
@@ -41,4 +41,4 @@ fun String.removeFirstChar() = removeCharAt(0)
fun String.toLocale(): Locale = Locale.Builder().setLanguageTag(this).build()
-fun LocaleVars.toWatcher(editText: EditText) = CurrencyInputWatcher(editText, currencySymbol, tag.toLocale())
+fun LocaleVars.toWatcher(editText: EditText, decimalPlaces: Int = 2) = CurrencyInputWatcher(editText, currencySymbol, tag.toLocale(), decimalPlaces)
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
index 44c3b61..66659b1 100644
--- a/sample/src/main/res/layout/activity_main.xml
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -11,6 +11,7 @@
app:currencySymbol="$"
app:useCurrencySymbolAsHint="true"
app:localeTag="en-NG"
+ app:maxNumberOfDecimalDigits="2"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:ems="10"