mirror of
https://github.com/hmalik144/EasyCC_Master.git
synced 2026-01-31 02:41:47 +00:00
Viewmodel updated to use live data
Main activity dialogs changed code clean up
This commit is contained in:
BIN
.idea/caches/build_file_checksums.ser
generated
BIN
.idea/caches/build_file_checksums.ser
generated
Binary file not shown.
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -39,7 +39,7 @@
|
|||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ android {
|
|||||||
def ccApiKey = properties.getProperty("cc_api_key", "")
|
def ccApiKey = properties.getProperty("cc_api_key", "")
|
||||||
|
|
||||||
it.buildConfigField 'String', "CC_API_KEY", ccApiKey
|
it.buildConfigField 'String', "CC_API_KEY", ccApiKey
|
||||||
|
|
||||||
it.resValue 'string', "api_key", ccApiKey
|
it.resValue 'string', "api_key", ccApiKey
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject
|
|||||||
/**
|
/**
|
||||||
* Main entry point for accessing currency data.
|
* Main entry point for accessing currency data.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface Repository {
|
interface Repository {
|
||||||
|
|
||||||
suspend fun getData(fromCurrency: String, toCurrency: String): ResponseObject
|
suspend fun getData(fromCurrency: String, toCurrency: String): ResponseObject
|
||||||
|
|||||||
@@ -7,14 +7,12 @@ import android.text.Editable
|
|||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.TextView
|
|
||||||
import com.appttude.h_mal.easycc.R
|
import com.appttude.h_mal.easycc.R
|
||||||
import kotlinx.android.synthetic.main.custom_dialog.*
|
import kotlinx.android.synthetic.main.custom_dialog.*
|
||||||
|
|
||||||
class CustomDialogClass(
|
class CustomDialogClass(
|
||||||
context: Context,
|
context: Context,
|
||||||
val textView: TextView,
|
private val clickListener: ClickListener
|
||||||
val viewModel: MainViewModel
|
|
||||||
) : Dialog(context) {
|
) : Dialog(context) {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -24,7 +22,11 @@ class CustomDialogClass(
|
|||||||
window!!.setBackgroundDrawableResource(android.R.color.transparent)
|
window!!.setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||||
|
|
||||||
val arrayAdapter = ArrayAdapter.createFromResource(context, R.array.currency_arrays, android.R.layout.simple_list_item_1)
|
val arrayAdapter =
|
||||||
|
ArrayAdapter.createFromResource(
|
||||||
|
context, R.array.currency_arrays,
|
||||||
|
android.R.layout.simple_list_item_1)
|
||||||
|
|
||||||
list_view.adapter = arrayAdapter
|
list_view.adapter = arrayAdapter
|
||||||
|
|
||||||
search_text.addTextChangedListener(object : TextWatcher {
|
search_text.addTextChangedListener(object : TextWatcher {
|
||||||
@@ -36,15 +38,12 @@ class CustomDialogClass(
|
|||||||
})
|
})
|
||||||
|
|
||||||
list_view.setOnItemClickListener{ adapterView, _, i, _ ->
|
list_view.setOnItemClickListener{ adapterView, _, i, _ ->
|
||||||
if (textView.tag == "top"){
|
clickListener.onText(adapterView.getItemAtPosition(i).toString())
|
||||||
viewModel.rateIdFrom = adapterView.getItemAtPosition(i).toString()
|
|
||||||
}else{
|
|
||||||
viewModel.rateIdTo = adapterView.getItemAtPosition(i).toString()
|
|
||||||
}
|
|
||||||
textView.text = adapterView.getItemAtPosition(i).toString()
|
|
||||||
viewModel.getExchangeRate()
|
|
||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ClickListener{
|
||||||
|
fun onText(currencyName: String)
|
||||||
|
}
|
||||||
@@ -3,12 +3,12 @@ package com.appttude.h_mal.easycc.mvvm.ui.app
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.util.Log
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import com.appttude.h_mal.easycc.R
|
import com.appttude.h_mal.easycc.R
|
||||||
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding
|
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding
|
||||||
@@ -20,42 +20,56 @@ import org.kodein.di.KodeinAware
|
|||||||
import org.kodein.di.android.kodein
|
import org.kodein.di.android.kodein
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClickListener{
|
class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClickListener {
|
||||||
|
|
||||||
override val kodein by kodein()
|
override val kodein by kodein()
|
||||||
private val factory : MainViewModelFactory by instance()
|
// Retrieve MainViewModelFactory via dependency injection
|
||||||
|
private val factory: MainViewModelFactory by instance()
|
||||||
|
|
||||||
companion object{
|
companion object {
|
||||||
lateinit var viewModel: MainViewModel
|
lateinit var viewModel: MainViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
// Keyboard is not overlapping views
|
||||||
|
this.window.setSoftInputMode(
|
||||||
|
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN or
|
||||||
|
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||||
|
|
||||||
this.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN or WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
viewModel = ViewModelProviders.of(this, factory)
|
||||||
|
.get(MainViewModel::class.java)
|
||||||
|
|
||||||
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
|
// Bind viewmodel to layout with view binding
|
||||||
viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)
|
DataBindingUtil
|
||||||
|
.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
|
||||||
binding.viewmodel = viewModel
|
.apply {
|
||||||
binding.lifecycleOwner = this
|
viewmodel = viewModel
|
||||||
|
lifecycleOwner = this@MainActivity
|
||||||
viewModel.rateListener = this
|
|
||||||
|
|
||||||
intent.extras?.apply {
|
|
||||||
val itemOne = getString("parse_1")
|
|
||||||
val itemTwo = getString("parse_2")
|
|
||||||
|
|
||||||
if (!itemOne.isNullOrEmpty() && !itemTwo.isNullOrEmpty()){
|
|
||||||
viewModel.rateIdTo = itemOne
|
|
||||||
viewModel.rateIdFrom = itemTwo
|
|
||||||
|
|
||||||
viewModel.getExchangeRate()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.start()
|
viewModel.initiate(intent.extras)
|
||||||
|
|
||||||
|
setUpListeners()
|
||||||
|
setUpObservers()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUpObservers() {
|
||||||
|
viewModel.operationStartedListener.observe(this, Observer {
|
||||||
|
progressBar.hideView(false)
|
||||||
|
})
|
||||||
|
viewModel.operationFinishedListener.observe(this, Observer { pair ->
|
||||||
|
progressBar.hideView(true)
|
||||||
|
if (pair.first){
|
||||||
|
bottomInsertValues.clearEditText()
|
||||||
|
topInsertValue.clearEditText()
|
||||||
|
}else{
|
||||||
|
pair.second?.let { DisplayToast(it) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUpListeners(){
|
||||||
topInsertValue.addTextChangedListener(textWatcherClass)
|
topInsertValue.addTextChangedListener(textWatcherClass)
|
||||||
bottomInsertValues.addTextChangedListener(textWatcherClass2)
|
bottomInsertValues.addTextChangedListener(textWatcherClass2)
|
||||||
|
|
||||||
@@ -63,8 +77,14 @@ class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClic
|
|||||||
currency_two.setOnClickListener(this)
|
currency_two.setOnClickListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showCustomDialog(view: View?){
|
private fun showCustomDialog(view: View?) {
|
||||||
val dialogClass = CustomDialogClass(this, view as TextView, viewModel)
|
|
||||||
|
val dialogClass = CustomDialogClass(this, object : ClickListener {
|
||||||
|
override fun onText(currencyName: String) {
|
||||||
|
(view as TextView).text = currencyName
|
||||||
|
viewModel.setCurrencyName(view.tag, currencyName)
|
||||||
|
}
|
||||||
|
})
|
||||||
dialogClass.show()
|
dialogClass.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,17 +110,13 @@ class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClic
|
|||||||
private val textWatcherClass: TextWatcher = object : TextWatcher {
|
private val textWatcherClass: TextWatcher = object : TextWatcher {
|
||||||
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
||||||
bottomInsertValues.removeTextChangedListener(textWatcherClass2)
|
bottomInsertValues.removeTextChangedListener(textWatcherClass2)
|
||||||
if (topInsertValue.text.isNullOrEmpty()) {
|
if (topInsertValue.text.isNullOrEmpty())
|
||||||
bottomInsertValues.setText("")
|
bottomInsertValues.setText("")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||||
override fun afterTextChanged(s: Editable) {
|
override fun afterTextChanged(s: Editable) {
|
||||||
try {
|
bottomInsertValues.setText(viewModel.getConversion(s.toString()))
|
||||||
viewModel.setBottomValue(s.toString(), bottomInsertValues)
|
|
||||||
} catch (e: NumberFormatException) {
|
|
||||||
Log.e(this.javaClass.simpleName, "no numbers inserted")
|
|
||||||
}
|
|
||||||
bottomInsertValues.addTextChangedListener(textWatcherClass2)
|
bottomInsertValues.addTextChangedListener(textWatcherClass2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,17 +124,13 @@ class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClic
|
|||||||
private val textWatcherClass2: TextWatcher = object : TextWatcher {
|
private val textWatcherClass2: TextWatcher = object : TextWatcher {
|
||||||
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
||||||
topInsertValue.removeTextChangedListener(textWatcherClass)
|
topInsertValue.removeTextChangedListener(textWatcherClass)
|
||||||
if (bottomInsertValues.text.isNullOrEmpty()) {
|
if (bottomInsertValues.text.isNullOrEmpty())
|
||||||
topInsertValue.clearEditText()
|
topInsertValue.clearEditText()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||||
override fun afterTextChanged(s: Editable) {
|
override fun afterTextChanged(s: Editable) {
|
||||||
try {
|
topInsertValue.setText(viewModel.getReciprocalConversion(s.toString()))
|
||||||
viewModel.setTopValue(s.toString(), topInsertValue)
|
|
||||||
} catch (e: NumberFormatException) {
|
|
||||||
Log.e(this.javaClass.simpleName, "no numbers inserted")
|
|
||||||
}
|
|
||||||
topInsertValue.addTextChangedListener(textWatcherClass)
|
topInsertValue.addTextChangedListener(textWatcherClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +1,105 @@
|
|||||||
package com.appttude.h_mal.easycc.mvvm.ui.app
|
package com.appttude.h_mal.easycc.mvvm.ui.app
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.repository.Repository
|
import com.appttude.h_mal.easycc.mvvm.data.repository.Repository
|
||||||
import com.appttude.h_mal.easycc.mvvm.utils.toTwoDp
|
import com.appttude.h_mal.easycc.mvvm.utils.toTwoDp
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
private const val TAG = "MainViewModel"
|
||||||
class MainViewModel(
|
class MainViewModel(
|
||||||
private val repository: Repository
|
private val repository: Repository
|
||||||
) : ViewModel(){
|
) : ViewModel(){
|
||||||
|
|
||||||
private val defaultValue by lazy { repository.getArrayList()[0] }
|
|
||||||
private val conversionPairs by lazy { repository.getConversionPair() }
|
private val conversionPairs by lazy { repository.getConversionPair() }
|
||||||
|
|
||||||
var rateIdFrom: String? = conversionPairs.first ?: defaultValue
|
var rateIdFrom: String? = null
|
||||||
var rateIdTo: String? = conversionPairs.second ?: defaultValue
|
var rateIdTo: String? = null
|
||||||
|
|
||||||
var topVal: String? = null
|
|
||||||
var bottomVal: String? = null
|
|
||||||
|
|
||||||
var rateListener: RateListener? = null
|
|
||||||
|
|
||||||
//operation results livedata based on outcome of operation
|
//operation results livedata based on outcome of operation
|
||||||
val operationSuccess = MutableLiveData<Pair<Boolean, String>>()
|
val operationStartedListener = MutableLiveData<Boolean>()
|
||||||
val currencyRate = MutableLiveData<Double>()
|
val operationFinishedListener = MutableLiveData<Pair<Boolean, String?>>()
|
||||||
|
|
||||||
private var conversionRate: Double = 0.00
|
private var conversionRate: Double = 0.00
|
||||||
|
|
||||||
fun getExchangeRate(){
|
fun getExchangeRate(){
|
||||||
rateListener?.onStarted()
|
|
||||||
|
operationStartedListener.postValue(false)
|
||||||
|
|
||||||
if (rateIdFrom.isNullOrEmpty() || rateIdTo.isNullOrEmpty()){
|
if (rateIdFrom.isNullOrEmpty() || rateIdTo.isNullOrEmpty()){
|
||||||
rateListener?.onFailure("Select currencies")
|
operationFinishedListener.postValue(Pair(false, "Select currencies"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
if (rateIdFrom == rateIdTo){
|
||||||
|
conversionRate = 1.00
|
||||||
|
operationFinishedListener.postValue(Pair(true, null))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
val exchangeResponse = repository.getData(rateIdFrom!!, rateIdTo!!)
|
val exchangeResponse = repository.getData(rateIdFrom!!, rateIdTo!!)
|
||||||
repository.setConversionPair(rateIdFrom!!, rateIdTo!!)
|
repository.setConversionPair(rateIdFrom!!, rateIdTo!!)
|
||||||
|
|
||||||
exchangeResponse.results?.iterator()?.next()?.value?.let {
|
exchangeResponse.results?.iterator()?.next()?.value?.let {
|
||||||
rateListener?.onSuccess()
|
operationFinishedListener.postValue(Pair(true, null))
|
||||||
conversionRate = it.value
|
conversionRate = it.value
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
}catch(e: IOException){
|
}catch(e: IOException){
|
||||||
rateListener?.onFailure(e.message ?: "Currency Retrieval failed")
|
operationFinishedListener.postValue(Pair(false, e.message ?: "Currency Retrieval failed"))
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
rateListener?.onFailure("Failed to retrieve rate")
|
|
||||||
|
operationFinishedListener.postValue(Pair(false, "Failed to retrieve rate"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBottomValue(fromValue : String, editText: EditText) {
|
fun getConversion(fromValue: String): String? {
|
||||||
|
return try {
|
||||||
val fromValDouble = fromValue.toDouble()
|
val fromValDouble = fromValue.toDouble()
|
||||||
val bottomVal1 = (fromValDouble * conversionRate).toTwoDp()
|
val bottomVal1 = (fromValDouble * conversionRate).toTwoDp()
|
||||||
editText.setText(bottomVal1.toBigDecimal().toPlainString())
|
bottomVal1.toBigDecimal().toPlainString()
|
||||||
|
}catch (e: NumberFormatException) {
|
||||||
|
Log.e(TAG, "no numbers inserted")
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTopValue(toValue : String, editText: EditText) {
|
fun getReciprocalConversion(toValue: String): String? {
|
||||||
|
return try {
|
||||||
val toDoubleVal = toValue.toDouble()
|
val toDoubleVal = toValue.toDouble()
|
||||||
val newTopVal = toDoubleVal.times((1/conversionRate)).toTwoDp()
|
val newTopVal = toDoubleVal.times((1/conversionRate)).toTwoDp()
|
||||||
editText.setText(newTopVal.toBigDecimal().toPlainString())
|
newTopVal.toBigDecimal().toPlainString()
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
Log.e(TAG, "no numbers inserted")
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(){
|
fun setCurrencyName(tag: Any?, currencyName: String){
|
||||||
if (rateIdFrom != rateIdTo){
|
if (tag.toString() == "top"){
|
||||||
|
rateIdFrom = currencyName
|
||||||
|
}else{
|
||||||
|
rateIdTo = currencyName
|
||||||
|
}
|
||||||
getExchangeRate()
|
getExchangeRate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun initiate(extras: Bundle?) {
|
||||||
|
rateIdFrom = extras?.getString("parse_1") ?: conversionPairs.first
|
||||||
|
rateIdTo = extras?.getString("parse_2") ?: conversionPairs.second
|
||||||
|
|
||||||
|
getExchangeRate()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,6 @@
|
|||||||
android:layout_marginTop="6dp"
|
android:layout_marginTop="6dp"
|
||||||
android:background="@drawable/round_edit_text"
|
android:background="@drawable/round_edit_text"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:text="@={viewmodel.topVal}"
|
|
||||||
android:hint="insert value one"
|
android:hint="insert value one"
|
||||||
android:textColorHighlight="#608d91"
|
android:textColorHighlight="#608d91"
|
||||||
android:inputType="numberDecimal"
|
android:inputType="numberDecimal"
|
||||||
@@ -96,7 +95,6 @@
|
|||||||
android:layout_weight="7"
|
android:layout_weight="7"
|
||||||
android:background="@drawable/round_edit_text"
|
android:background="@drawable/round_edit_text"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:text="@={viewmodel.bottomVal}"
|
|
||||||
android:hint="insert value two"
|
android:hint="insert value two"
|
||||||
android:textColorHighlight="#608d91"
|
android:textColorHighlight="#608d91"
|
||||||
android:inputType="numberDecimal"
|
android:inputType="numberDecimal"
|
||||||
|
|||||||
Reference in New Issue
Block a user