diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index f449c57..2763ef0 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/.idea/misc.xml b/.idea/misc.xml index 99e86f7..d04819c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -39,7 +39,7 @@ - + diff --git a/app/build.gradle b/app/build.gradle index c054325..bc52284 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,9 +34,7 @@ android { def ccApiKey = properties.getProperty("cc_api_key", "") it.buildConfigField 'String', "CC_API_KEY", ccApiKey - it.resValue 'string', "api_key", ccApiKey - } } diff --git a/app/src/main/java/com/appttude/h_mal/easycc/mvvm/data/repository/Repository.kt b/app/src/main/java/com/appttude/h_mal/easycc/mvvm/data/repository/Repository.kt index 907ed58..3b59944 100644 --- a/app/src/main/java/com/appttude/h_mal/easycc/mvvm/data/repository/Repository.kt +++ b/app/src/main/java/com/appttude/h_mal/easycc/mvvm/data/repository/Repository.kt @@ -5,7 +5,6 @@ import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject /** * Main entry point for accessing currency data. */ - interface Repository { suspend fun getData(fromCurrency: String, toCurrency: String): ResponseObject diff --git a/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/CustomDialogClass.kt b/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/CustomDialogClass.kt index 2a6c6c5..ae1d70e 100644 --- a/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/CustomDialogClass.kt +++ b/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/CustomDialogClass.kt @@ -7,14 +7,12 @@ import android.text.Editable import android.text.TextWatcher import android.view.WindowManager import android.widget.ArrayAdapter -import android.widget.TextView import com.appttude.h_mal.easycc.R import kotlinx.android.synthetic.main.custom_dialog.* class CustomDialogClass( context: Context, - val textView: TextView, - val viewModel: MainViewModel + private val clickListener: ClickListener ) : Dialog(context) { override fun onCreate(savedInstanceState: Bundle?) { @@ -24,7 +22,11 @@ class CustomDialogClass( window!!.setBackgroundDrawableResource(android.R.color.transparent) 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 search_text.addTextChangedListener(object : TextWatcher { @@ -36,15 +38,12 @@ class CustomDialogClass( }) list_view.setOnItemClickListener{ adapterView, _, i, _ -> - if (textView.tag == "top"){ - viewModel.rateIdFrom = adapterView.getItemAtPosition(i).toString() - }else{ - viewModel.rateIdTo = adapterView.getItemAtPosition(i).toString() - } - textView.text = adapterView.getItemAtPosition(i).toString() - viewModel.getExchangeRate() - + clickListener.onText(adapterView.getItemAtPosition(i).toString()) dismiss() } } +} + +interface ClickListener{ + fun onText(currencyName: String) } \ No newline at end of file diff --git a/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/MainActivity.kt b/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/MainActivity.kt index e8d742c..874b382 100644 --- a/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/MainActivity.kt +++ b/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/MainActivity.kt @@ -3,12 +3,12 @@ package com.appttude.h_mal.easycc.mvvm.ui.app import android.os.Bundle import android.text.Editable import android.text.TextWatcher -import android.util.Log import android.view.View import android.view.WindowManager import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import com.appttude.h_mal.easycc.R 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.generic.instance -class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClickListener{ +class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClickListener { 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 } override fun onCreate(savedInstanceState: Bundle?) { 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) - viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java) - - binding.viewmodel = viewModel - binding.lifecycleOwner = this - - 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() - } + // Bind viewmodel to layout with view binding + DataBindingUtil + .setContentView(this, R.layout.activity_main) + .apply { + viewmodel = viewModel + lifecycleOwner = this@MainActivity } - 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) bottomInsertValues.addTextChangedListener(textWatcherClass2) @@ -63,8 +77,14 @@ class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClic currency_two.setOnClickListener(this) } - private fun showCustomDialog(view: View?){ - val dialogClass = CustomDialogClass(this, view as TextView, viewModel) + private fun showCustomDialog(view: View?) { + + val dialogClass = CustomDialogClass(this, object : ClickListener { + override fun onText(currencyName: String) { + (view as TextView).text = currencyName + viewModel.setCurrencyName(view.tag, currencyName) + } + }) dialogClass.show() } @@ -90,17 +110,13 @@ class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClic private val textWatcherClass: TextWatcher = object : TextWatcher { override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) { bottomInsertValues.removeTextChangedListener(textWatcherClass2) - if (topInsertValue.text.isNullOrEmpty()) { + if (topInsertValue.text.isNullOrEmpty()) bottomInsertValues.setText("") - } } + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun afterTextChanged(s: Editable) { - try { - viewModel.setBottomValue(s.toString(), bottomInsertValues) - } catch (e: NumberFormatException) { - Log.e(this.javaClass.simpleName, "no numbers inserted") - } + bottomInsertValues.setText(viewModel.getConversion(s.toString())) bottomInsertValues.addTextChangedListener(textWatcherClass2) } } @@ -108,17 +124,13 @@ class MainActivity : AppCompatActivity(), RateListener, KodeinAware, View.OnClic private val textWatcherClass2: TextWatcher = object : TextWatcher { override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) { topInsertValue.removeTextChangedListener(textWatcherClass) - if (bottomInsertValues.text.isNullOrEmpty()) { + if (bottomInsertValues.text.isNullOrEmpty()) topInsertValue.clearEditText() - } } + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun afterTextChanged(s: Editable) { - try { - viewModel.setTopValue(s.toString(), topInsertValue) - } catch (e: NumberFormatException) { - Log.e(this.javaClass.simpleName, "no numbers inserted") - } + topInsertValue.setText(viewModel.getReciprocalConversion(s.toString())) topInsertValue.addTextChangedListener(textWatcherClass) } } diff --git a/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/MainViewModel.kt b/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/MainViewModel.kt index 96d277f..1526bd7 100644 --- a/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/MainViewModel.kt +++ b/app/src/main/java/com/appttude/h_mal/easycc/mvvm/ui/app/MainViewModel.kt @@ -1,79 +1,105 @@ package com.appttude.h_mal.easycc.mvvm.ui.app +import android.os.Bundle +import android.util.Log import android.widget.EditText import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.appttude.h_mal.easycc.mvvm.data.repository.Repository import com.appttude.h_mal.easycc.mvvm.utils.toTwoDp +import kotlinx.android.synthetic.main.activity_main.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.IOException import java.text.DecimalFormat +private const val TAG = "MainViewModel" class MainViewModel( private val repository: Repository ) : ViewModel(){ - private val defaultValue by lazy { repository.getArrayList()[0] } private val conversionPairs by lazy { repository.getConversionPair() } - var rateIdFrom: String? = conversionPairs.first ?: defaultValue - var rateIdTo: String? = conversionPairs.second ?: defaultValue - - var topVal: String? = null - var bottomVal: String? = null - - var rateListener: RateListener? = null + var rateIdFrom: String? = null + var rateIdTo: String? = null //operation results livedata based on outcome of operation - val operationSuccess = MutableLiveData>() - val currencyRate = MutableLiveData() + val operationStartedListener = MutableLiveData() + val operationFinishedListener = MutableLiveData>() private var conversionRate: Double = 0.00 fun getExchangeRate(){ - rateListener?.onStarted() + + operationStartedListener.postValue(false) + if (rateIdFrom.isNullOrEmpty() || rateIdTo.isNullOrEmpty()){ - rateListener?.onFailure("Select currencies") + operationFinishedListener.postValue(Pair(false, "Select currencies")) return } - CoroutineScope(Dispatchers.Main).launch { + if (rateIdFrom == rateIdTo){ + conversionRate = 1.00 + operationFinishedListener.postValue(Pair(true, null)) + return + } + + CoroutineScope(Dispatchers.IO).launch { try { val exchangeResponse = repository.getData(rateIdFrom!!, rateIdTo!!) repository.setConversionPair(rateIdFrom!!, rateIdTo!!) exchangeResponse.results?.iterator()?.next()?.value?.let { - rateListener?.onSuccess() + operationFinishedListener.postValue(Pair(true, null)) conversionRate = it.value return@launch } - }catch(e: IOException){ - rateListener?.onFailure(e.message ?: "Currency Retrieval failed") + operationFinishedListener.postValue(Pair(false, e.message ?: "Currency Retrieval failed")) return@launch } - rateListener?.onFailure("Failed to retrieve rate") + + operationFinishedListener.postValue(Pair(false, "Failed to retrieve rate")) } } - fun setBottomValue(fromValue : String, editText: EditText) { - val fromValDouble = fromValue.toDouble() - val bottomVal1 = (fromValDouble * conversionRate).toTwoDp() - editText.setText(bottomVal1.toBigDecimal().toPlainString()) - } - - fun setTopValue(toValue : String, editText: EditText) { - val toDoubleVal = toValue.toDouble() - val newTopVal = toDoubleVal.times((1/conversionRate)).toTwoDp() - editText.setText(newTopVal.toBigDecimal().toPlainString()) - } - - fun start(){ - if (rateIdFrom != rateIdTo){ - getExchangeRate() + fun getConversion(fromValue: String): String? { + return try { + val fromValDouble = fromValue.toDouble() + val bottomVal1 = (fromValDouble * conversionRate).toTwoDp() + bottomVal1.toBigDecimal().toPlainString() + }catch (e: NumberFormatException) { + Log.e(TAG, "no numbers inserted") + null } } + fun getReciprocalConversion(toValue: String): String? { + return try { + val toDoubleVal = toValue.toDouble() + val newTopVal = toDoubleVal.times((1/conversionRate)).toTwoDp() + newTopVal.toBigDecimal().toPlainString() + } catch (e: NumberFormatException) { + Log.e(TAG, "no numbers inserted") + null + } + } + + fun setCurrencyName(tag: Any?, currencyName: String){ + if (tag.toString() == "top"){ + rateIdFrom = currencyName + }else{ + rateIdTo = currencyName + } + getExchangeRate() + } + + fun initiate(extras: Bundle?) { + rateIdFrom = extras?.getString("parse_1") ?: conversionPairs.first + rateIdTo = extras?.getString("parse_2") ?: conversionPairs.second + + getExchangeRate() + } + } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 5109428..87220f7 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -58,7 +58,6 @@ android:layout_marginTop="6dp" android:background="@drawable/round_edit_text" android:ems="10" - android:text="@={viewmodel.topVal}" android:hint="insert value one" android:textColorHighlight="#608d91" android:inputType="numberDecimal" @@ -96,7 +95,6 @@ android:layout_weight="7" android:background="@drawable/round_edit_text" android:ems="10" - android:text="@={viewmodel.bottomVal}" android:hint="insert value two" android:textColorHighlight="#608d91" android:inputType="numberDecimal"