Formating a string from right to left for currency values - android

I'm working on creating a way to input an amount and format it from left to right with placeholder zeros.
For example, pressing 1 and 2 would show $0.12 pressing 3 would give $1.23. Pressing backspace would give $0.12.
Instead, I am getting $1,0002.00
binding.keypad.btnBackspace.setOnClickListener {
val text = binding.tvTotalValue.text.toString()
if(text.isNotEmpty()) {
binding.tvTotalValue.text = text.drop(1)
}
binding.tvTotalValue.text = ""
}
binding.keypad.onNumberSelected = {
processNewAmount(it.toString())
}
private fun processNewAmount(newValue: String) {
val text = binding.tvTotalValue.text.toString().plus(newValue)
val result = text.replace("[^0-9]".toRegex(), "") // remove any characters
val amount = result.toDouble()
binding.tvTotalValue.text = NumberFormat.getCurrencyInstance().format(amount)
}
What am I doing wrong?
Is there a better way to do this?

I advise keeping a property that stores the entered value without formatting. Each time a number is added, you can add it to this entered number and then format it for the screen. That will be a lot simpler than trying to move/remove existing symbols around in the String.
private var enteredNumber = ""
//...
binding.keypad.btnBackspace.setOnClickListener {
enteredNumber = enteredNumber.dropLast(1)
refreshTotal()
}
binding.keypad.onNumberSelected = {
if(!(it == 0 && enteredNumber == "0")) { // avoid multiple zeros or backspace will act unexpectedly
enteredNumber += it.toString()
refreshTotal()
}
}
//...
private fun refreshTotal() {
val amount = enteredNumber.toDouble() / 100.0
binding.tvTotalValue.text =
NumberFormat.getCurrencyInstance().format(amount)
}

Related

how to handle new line in word count?

i'm referring this answer https://stackoverflow.com/a/42802060/12428090
basically my goal is to handle word count of 100 words
the abve solution works fine for typed 100 words and even handles copy paste very well
but it doesnt handles the new line case
suppose in entered word or copy pasted word contains new line then the word count is returning incorrect
following is the code please help out
override fun onTextChanged(s: CharSequence, start: Int, before: Int,
count: Int) {
val wordsLength: Int = countWords(s.toString()) // words.length;
limitmoderator.text = "$wordsLength/$MAX_WORDS"
val yourText: String =
moderator_intro.getText().toString().replace(160.toChar().toString(), " ")
if (yourText.split("\\s+".toRegex()).size > MAX_WORDS) {
var space = 0
var length = 0
for (i in 0 until yourText.length) {
if (yourText[i] == ' ') {
space++
if (space >= MAX_WORDS) {
length = i
break
}
}
}
if (length > 1) {
moderator_intro.getText()
.delete(length, yourText.length) // deleting last exceeded words
setCharLimit(moderator_intro, length - 1) //limit edit text
}
} else {
removeFilter(moderator_intro)
}}
private fun countWords(s: String): Int {
val trim = s.trim()
return if (trim.isEmpty()) 0 else trim.split("\\s+".toRegex()).size
// separate string around spaces
}
private var filter: InputFilter? = null
private fun setCharLimit(et: EditText, max: Int) {
filter = LengthFilter(max)
et.filters = arrayOf<InputFilter>(filter as LengthFilter)
}
private fun removeFilter(et: EditText) {
if (filter != null) {
et.filters = arrayOfNulls(0)
filter = null
}
}
so i have tried rplacing the "\n" in the text but it doesnt seems to be handling the case properly
any help will be appreciated
thanks in advance
Here's a different strategy than the one from the question you linked. Notice I'm using afterTextChanged and not onTextChanged!
I'm manually counting words to get the character index of the first whitespace after the last allowable word. That way I don't have to trim and then use Regex, and then try to figure out the index offset of that Regex. Then instead of applying a temporary filter, I directly cut the end of the Editable off.
editText.setSelection is to keep the cursor from jumping to the beginning.
override fun afterTextChanged(s: Editable) {
var previousWasWhitespace = true
var i = 0
var wordCount = 0
for (c in s) {
val whitespace = c.isWhitespace()
if (whitespace && !previousWasWhitespace && ++wordCount == MAX_WORDS) {
break
}
previousWasWhitespace = whitespace
i++
}
if (i < s.length) {
s.delete(i, s.length)
editText.setSelection(i)
}
}
You could write a regular expression to match the text that you want to keep and remove the rest. In this case you want match (non-whitespace+)(whitespace*) maximum 100 times.
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
val input = moderator_intro.getText().toString()
val maxWords = 100
val whiteSpace = "\\p{javaWhitespace}\u00A0\u2007\u202F" //All white space characters
val keepRegex = Regex("[$whiteSpace]*([^$whiteSpace]+[$whiteSpace]*){1,${maxWords.toString()}}")
val allowedText = keepRegex.find(input)?.value ?: ""
val wordAmount = allowedText.split(Regex("[$whiteSpace]+")).filter { it.isNotBlank() }.size
val trailingWhiteSpace = Regex("[$whiteSpace]+$")
if(wordAmount == maxWords && allowedText.contains(trailingWhiteSpace)) {
val newText = allowedText.replace(trailingWhiteSpace, "")
moderator_intro.getText().delete(allowedText.length, input.length)
setCharLimit(moderator_intro, newText.length)
} else {
removeFilter(moderator_intro)
}
}

Calculator rounding to fit in editText

I have an editText limited to 14 characters. This works to limit the number the user inputs but it just cuts off numbers bigger than 14 characters(even after the decimal) in the answer. So, two problems I need help with. How do I make it so that errors if the value is larger than 14 digits, and to always round to the best place(like if the sum is 164541254.4568727(17char), it will round to 164541254.4569(14char) or 12.9876543210987 rounds to 12.9876543211)
Equal button code(because I think this is where I'll need to put the code):
fun equal(view: View) {
secondnum = editText.text.toString()
decpressed = 0
var sum = 0.0
when (op) {
"+" -> {
sum = (firstnum.toDouble() + secondnum.toDouble())
}
"-" -> {
sum = (firstnum.toDouble() - secondnum.toDouble())
}
"*" -> {
sum = (firstnum.toDouble() * secondnum.toDouble())
}
"/" -> {
if (secondnum == "0") {
Toast.makeText(this, "Can not divide by 0!", Toast.LENGTH_LONG).show()
editText.setText("0")
textView.text = ""
} else {
sum = (firstnum.toDouble() / secondnum.toDouble())
}
}
}
val out = DecimalFormat("#.##########")
out.roundingMode = RoundingMode.HALF_EVEN
editText.setText(out.format(sum))
textView.text = "$firstnum $op $secondnum ="
zero = true
}
If other code is needed, please request it.

Kotlin how to format Double number to String without zero after comma

Suppose I have Double number which I want to convert to String.
I want to have an option which based on that I would have String number without trailing zeroes.
So for example:
option with trailing zeros 123.00 -> "123,00", 123.324 -> "123,32"
option without trailing zeroes 123.00 -> "123", 123.324 -> "123,32"
Is there a nice way to do this in Kotlin?
This is code which I have which I feel is rather ugly:
private const val VALUE_DIVIDER = 100
private const val DIGITS_AMOUNT = 2
private val defaultLocale = Locale("us")
private val currency = "$"
private val cents = 10000
fun print(withoutTrailingZeros: Boolean = true): String {
val price = (cents.toDouble() / VALUE_DIVIDER)
.valueToString()
.let { if (withoutTrailingZeros) it.removeSuffix(",00") else it }
return "$price $currency"
}
private fun Double.valueToString() = round(DIGITS_AMOUNT).replace(".", ",")
private fun Double.round(digits: Int): String =
NumberFormat.getInstance(defaultLocale).apply {
maximumFractionDigits = digits
minimumFractionDigits = digits
isGroupingUsed = false
}.format(this)
UPDATE: The solution provided by #Roma Pochanin works partially, but strangely only as jUnit tests.
After I am running integration tests on Android emulator using this logic this is not working for 0 (it is formatted as "0,00" even when the withoutTrailingZeros flag is true). I heard about some bug related to that Why does new BigDecimal("0.0").stripTrailingZeros() have a scale of 1?
but how it is connected with my case? Can anyone explain?
Please, see the exact sessions from debugger:
working, as jUnit tests: https://ibb.co/HN9n41T
bug, when running instrumentation tests on Android emulator: https://ibb.co/VCrmrMh
There is no function for that in the Kotlin standard library, but you can specify the number of decimal places and the decimal format symbol using Java's DecimalFormat:
val formatSymbols = DecimalFormatSymbols.getInstance().apply {
decimalSeparator = ','
}
val twoDecimalDigitsFormat = DecimalFormat("#.##").apply {
decimalFormatSymbols = formatSymbols
}
val twoTrailingZerosFormat = DecimalFormat("#.00").apply {
decimalFormatSymbols = formatSymbols
}
fun formatPrice(price: Double, withDecimalZeros: Boolean) = if (withDecimalZeros) {
twoTrailingZerosFormat
} else {
// Is number still the same after discarding places?
if (price.toInt().toDouble() == price) {
twoDecimalDigitsFormat
} else {
twoTrailingZerosFormat
}
}.format(price)
println(formatPrice(123.00, true)) // 123,00
println(formatPrice(123.324, true)) // 132,32
println(formatPrice(123.00, false)) // 123
println(formatPrice(123.324, false)) // 123,32
Why don't you use BigDecimal? It's like the default way to deal with prices and similar stuff. You also can consider using BigDecimal's method stripTrailingZeros:
private const val VALUE_DIVIDER = 100
private const val DIGITS_AMOUNT = 2
private val currency = "$"
private val cents = 1298379
fun formatPrice(withoutDecimalZeros: Boolean = true) =
BigDecimal(cents)
.divide(BigDecimal(VALUE_DIVIDER), DIGITS_AMOUNT, RoundingMode.UP)
.let { if (withoutDecimalZeros) it.stripTrailingZeros() else it }
.toString().replace(".", ",")
.let { "$it $currency" }

Android implement text completion on swipe

How can I implement text completion,Like Gmail's smart compose?
I've an edit text where the user enters server address and I want to detect when they start typing the domain suffix and suggest completion.
Thanks.
First you need an algorithm to get suggestion from a given dictionary.
I've created a simple class named SuggestionManager to get suggestion from a given dictionary for a string input. Instead of returning the full match, it'll only return the remaining part of the given input. Below given a simple unit test along with full source code of the class. You can also go here to run the test online.
SuggestionManager.kt
class SuggestionManager(private val dictionary: Array<String>) {
companion object {
private val WORD_SPLIT_REGEX = Regex("[^A-Za-z0-9'\\-]")
/**
* To get reversed list
*/
private fun getReversedList(list: List<String>): MutableSet<String> {
val reversed = mutableSetOf<String>()
for (item in list.withIndex()) {
if (item.index != 0) {
val rev = list.subList(list.size - item.index, list.size).joinToString(" ")
reversed.add(rev)
}
}
// finally, add the full string
reversed.add(list.joinToString(" "))
return reversed
}
}
fun getSuggestionFor(_text: String?): String? {
var text = _text
// empty text
if (text.isNullOrBlank()) {
return null
}
// Getting last line only
if (text.contains("\n")) {
text = text.split("\n").last()
if (text.trim().isEmpty()) {
return null
}
}
// Splitting words by space
val words = text.split(WORD_SPLIT_REGEX).filter { it.isNotBlank() }
// Getting last word
val lastWord = if (text.endsWith(" ")) "${words.last()} " else words.last()
// Matching if last word exist in any dictionary
val suggestions = mutableSetOf<String>()
for (dicItem in dictionary) {
if (dicItem.contains(lastWord, true)) {
// Storing founded matches
suggestions.add(dicItem)
}
}
// Reverse ordering split-ed words
val pyramidWords = getReversedList(words)
val matchList = mutableListOf<String>()
for (pw in pyramidWords) {
for (sug in suggestions) {
// Storing suggestions starts with reversed word
if (sug.startsWith(pw, true)) {
matchList.add("$pw:$sug")
}
}
}
// Checking if second level match is not empty
if (matchList.isNotEmpty()) {
// Ordering by matched reversed word - (largest key first)
matchList.sortBy { -it.split(":")[0].length }
// Looping through in ascending order
for (m in matchList) {
val match = m.split(":")
val selPw: String = match[0]
var selSug: String = match.subList(1, match.size).joinToString(":")
// trimming to
selSug = selSug.replace(selPw, "", true)
if (text.endsWith(" ")) {
selSug = selSug.trim()
}
return selSug
}
}
return null
}
}
Unit Test
class SuggestionManagerUrlTest {
private val suggestionManager by lazy {
val dictionary = arrayOf(
"google.com",
"facebook.com",
"gmail.com",
"yahoo.com",
"192.168.354.45"
)
SuggestionManager(dictionary)
}
#Test
fun test() {
// null of empty and null input
assertNull(suggestionManager.getSuggestionFor(null))
assertNull(suggestionManager.getSuggestionFor(""))
// matching
assertEquals("ogle.com", suggestionManager.getSuggestionFor("go"))
assertEquals("book.com", suggestionManager.getSuggestionFor("face"))
assertEquals(".168.354.45", suggestionManager.getSuggestionFor("192"))
// no match
assertNull(suggestionManager.getSuggestionFor("somesite"))
}
}
Then, you'll have to set text in EditText with two colors. One for input and other for the suggestion. You may use the Html.fromHtml method to do this.
val text = "<font color=#cc0029>$input</font> <font color=#ffcc00>$suggestion</font>";
yourEditText.setText(Html.fromHtml(text));
Combining these two aspects, you can create a custom EditText.

How to set a list/array to a TextView

I have a mutable list that changes each time the generate() function is called. What I'm trying to do is convert it to a string and set it to a TextView. The way I set the TextView below works for Integers but not for lists. It just does not display the contents of the list and I have no idea why it won't work. Instead the TextView2 does this: Genereated Numbers: []
val text = findViewById<TextView>(R.id.textView)
val text2 = findViewById<TextView>(R.id.textView2)
var possibleInputs = mutableListOf(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
fun generate() {
var num = (0..20).shuffled().last()
when {
possibleInputs.size == 0 -> text.text = "Done"
num in possibleInputs -> {
text.text = "$num"
text2.text = "Generated Numbers: $possibleInputs"
possibleInputs.remove(num)
}
else -> generate()
}
}
This is the code I used to fix it
for (i in 0 until possibleInputs.size) {
text2.append(possibleInputs[i].toString())
}

Categories

Resources