1+ package com.redmadrobot.inputmask
2+
3+ import android.icu.number.LocalizedNumberFormatter
4+ import android.icu.number.NumberFormatter
5+ import android.text.TextWatcher
6+ import android.widget.EditText
7+ import com.redmadrobot.inputmask.helper.AffinityCalculationStrategy
8+ import com.redmadrobot.inputmask.helper.Mask
9+ import com.redmadrobot.inputmask.model.CaretString
10+ import com.redmadrobot.inputmask.model.Notation
11+ import java.math.RoundingMode
12+ import java.util.*
13+
14+ open class NumberInputListener (
15+ primaryFormat : String ,
16+ affineFormats : List <String > = emptyList(),
17+ customNotations : List <Notation > = emptyList(),
18+ affinityCalculationStrategy : AffinityCalculationStrategy = AffinityCalculationStrategy .WHOLE_STRING ,
19+ autocomplete : Boolean = true ,
20+ autoskip : Boolean = false ,
21+ field : EditText ,
22+ listener : TextWatcher ? = null ,
23+ valueListener : ValueListener ? = null ,
24+ rightToLeft : Boolean = false
25+ ): MaskedTextChangedListener(
26+ primaryFormat,
27+ affineFormats,
28+ customNotations,
29+ affinityCalculationStrategy,
30+ autocomplete,
31+ autoskip,
32+ field,
33+ listener,
34+ valueListener,
35+ rightToLeft
36+ ) {
37+ /* *
38+ * Convenience constructor.
39+ */
40+ constructor (field: EditText ):
41+ this (field, null )
42+
43+ /* *
44+ * Convenience constructor.
45+ */
46+ constructor (field: EditText , valueListener: ValueListener ? ):
47+ this (field, null , valueListener)
48+
49+ /* *
50+ * Convenience constructor.
51+ */
52+ constructor (field: EditText , listener: TextWatcher ? , valueListener: ValueListener ? ):
53+ this (true , false , field, listener, valueListener)
54+
55+ /* *
56+ * Convenience constructor.
57+ */
58+ constructor (
59+ autocomplete: Boolean ,
60+ autoskip: Boolean ,
61+ field: EditText ,
62+ listener: TextWatcher ? ,
63+ valueListener: ValueListener ?
64+ ):
65+ this (
66+ " " ,
67+ emptyList(),
68+ emptyList(),
69+ AffinityCalculationStrategy .WHOLE_STRING ,
70+ autocomplete,
71+ autoskip,
72+ field,
73+ listener,
74+ valueListener
75+ )
76+
77+ open var formatter: LocalizedNumberFormatter =
78+ NumberFormatter
79+ .withLocale(Locale (" en_us" ))
80+ .roundingMode(RoundingMode .FLOOR )
81+
82+ override fun placeholder (): String {
83+ val text = " 0"
84+ val mask = pickMask(
85+ CaretString (text, text.length, CaretString .CaretGravity .FORWARD (autocomplete))
86+ )
87+ return mask.placeholder()
88+ }
89+
90+ override fun pickMask (text : CaretString ): Mask {
91+ val sanitisedNumberString = extractNumberAndDecimalSeparator(formatter, text.string)
92+
93+ val intNum = sanitisedNumberString.intPart.toLong()
94+ val intMaskFormat = formatter.format(intNum)
95+
96+ // guard let intNum = NumberFormatter().number(from: sanitisedNumberString.intPart), let intMaskFormat = formatter.string(from: intNum)
97+ // else {
98+ // return try! Mask.getOrCreate(withFormat: "[…]")
99+ // }
100+
101+ val intZero = intNum == 0 .toLong()
102+ val notationChar = assignNonZeroNumberNotation()
103+
104+ var maskFormat = " "
105+ var first = true
106+ intMaskFormat.toString().forEach { c: Char ->
107+ if (c.isDigit()) {
108+ if (first && ! intZero) {
109+ maskFormat + = " [$notationChar ]"
110+ first = false
111+ } else {
112+ maskFormat + = " [0]"
113+ }
114+ } else {
115+ maskFormat + = " {$c }"
116+ }
117+ }
118+
119+ if (sanitisedNumberString.numberOfOccurrencesOfDecimalSeparator > 0 ) {
120+ maskFormat + = " {${sanitisedNumberString.expectedDecimalSeparator} }"
121+ }
122+
123+ sanitisedNumberString.decPart.forEach { c: Char ->
124+ maskFormat + = " [0]"
125+ }
126+
127+ primaryFormat = maskFormat
128+ return super .pickMask(text)
129+ }
130+
131+ private data class SanitisedNumberString (
132+ val intPart : String ,
133+ val decPart : String ,
134+ val expectedDecimalSeparator : String ,
135+ val numberOfOccurrencesOfDecimalSeparator : Int
136+ )
137+
138+ private fun extractNumberAndDecimalSeparator (
139+ formatter : LocalizedNumberFormatter ,
140+ text : String
141+ ): SanitisedNumberString {
142+ // val appliedDecimalSeparator = formatter.decimalSeparator ?? NumberInputListener.decimalSeparator
143+ // val appliedCurrencyDecimalSeparator = formatter.currencyDecimalSeparator ?? NumberInputListener.decimalSeparator
144+
145+ val expectedDecimalSeparator: String = decimalSeparator
146+ // if (text.contains(appliedCurrencyDecimalSeparator)) {
147+ // expectedDecimalSeparator = appliedCurrencyDecimalSeparator
148+ // }
149+
150+ // var digitsAndDecimalSeparators = text.replace(ep)
151+ // .replacingOccurrences(of: appliedDecimalSeparator, with: NumberInputListener.decimalSeparator)
152+ // .replacingOccurrences(of: appliedCurrencyDecimalSeparator, with: NumberInputListener.decimalSeparator)
153+ // .filter { c in
154+ // return CharacterSet.decimalDigits.isMember(character: c) || String(c) == NumberInputListener.decimalSeparator
155+ // }
156+
157+
158+ var digitsAndDecimalSeparators = text.filter { c: Char -> c.isDigit() || c.toString() == decimalSeparator }
159+
160+ // val numberOfOccurencesOfDecimalSeparator = digitsAndDecimalSeparators.numberOfOccurencesOf(NumberInputListener.decimalSeparator)
161+ val numberOfOccurencesOfDecimalSeparator = digitsAndDecimalSeparators.count { c: Char -> c.toString() == decimalSeparator }
162+ if (numberOfOccurencesOfDecimalSeparator > 1 ) {
163+ // digitsAndDecimalSeparators =
164+ // digitsAndDecimalSeparators
165+ // .reversed()
166+ // .replace()
167+ // .repl(decimalSeparator, with: "", maxReplacements: numberOfOccurencesOfDecimalSeparator - 1)
168+ // .reversed
169+ }
170+
171+ val components = digitsAndDecimalSeparators.split(decimalSeparator)
172+
173+ var intStr = " "
174+ var decStr = " "
175+
176+ if (components.size > 1 ) {
177+ intStr = components.first()
178+ decStr = components.last()
179+ } else {
180+ intStr = components.first()
181+ }
182+
183+ intStr = if (intStr.isEmpty()) " 0" else intStr
184+ // intStr = String(intStr.prefix(formatter.maximumIntegerDigits))
185+ // decStr = String(decStr.prefix(formatter.maximumFractionDigits))
186+
187+ return SanitisedNumberString (
188+ intStr,
189+ decStr,
190+ expectedDecimalSeparator,
191+ numberOfOccurencesOfDecimalSeparator
192+ )
193+ }
194+
195+ private fun assignNonZeroNumberNotation (): Char {
196+ val character = ' 1'
197+ customNotations = listOf (
198+ Notation (
199+ character,
200+ " 123456789" ,
201+ false
202+ )
203+ )
204+ return character
205+ }
206+
207+ companion object {
208+ val decimalSeparator = " ."
209+
210+ /* *
211+ * Create a `NumberInputListener` instance and assign it as a field's
212+ * `TextWatcher` and `onFocusChangeListener`.
213+ */
214+ fun installOn (
215+ editText : EditText ,
216+ valueListener : ValueListener ? = null
217+ ): NumberInputListener = installOn(
218+ editText,
219+ true ,
220+ false ,
221+ null ,
222+ valueListener
223+ )
224+
225+ /* *
226+ * Create a `NumberInputListener` instance and assign it as a field's
227+ * `TextWatcher` and `onFocusChangeListener`.
228+ */
229+ fun installOn (
230+ editText : EditText ,
231+ autocomplete : Boolean = true,
232+ autoskip : Boolean = false,
233+ listener : TextWatcher ? = null,
234+ valueListener : ValueListener ? = null
235+ ): NumberInputListener {
236+ val maskedListener = NumberInputListener (
237+ autocomplete,
238+ autoskip,
239+ editText,
240+ listener,
241+ valueListener
242+ )
243+ editText.addTextChangedListener(maskedListener)
244+ editText.onFocusChangeListener = maskedListener
245+ return maskedListener
246+ }
247+ }
248+ }
0 commit comments