mirror of
https://github.com/hmalik144/EasyCC_Master.git
synced 2025-12-10 03:05:29 +00:00
Issue resolved
Took 2 hours 5 minutes
This commit is contained in:
@@ -1,12 +1,7 @@
|
||||
package com.appttude.h_mal.easycc.data.network.api
|
||||
|
||||
import com.appttude.h_mal.easycc.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.easycc.data.network.response.CurrencyResponse
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
package com.appttude.h_mal.easycc.data.network.api
|
||||
|
||||
import com.appttude.h_mal.easycc.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.easycc.data.network.interceptors.QueryInterceptor
|
||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
/**
|
||||
* Retrofit Network class to currency api calls
|
||||
*/
|
||||
interface CurrencyApi : Api{
|
||||
interface CurrencyApi : Api {
|
||||
|
||||
// Get rate from server with arguments passed in Repository
|
||||
@GET("convert?")
|
||||
|
||||
@@ -5,7 +5,7 @@ import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import javax.inject.Inject
|
||||
|
||||
class RemoteDataSource @Inject constructor(){
|
||||
class RemoteDataSource @Inject constructor() {
|
||||
|
||||
fun <Api> buildApi(
|
||||
okkHttpclient: OkHttpClient,
|
||||
|
||||
@@ -5,22 +5,22 @@ import com.appttude.h_mal.easycc.models.CurrencyModelInterface
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CurrencyResponse(
|
||||
@field:SerializedName("date")
|
||||
val date: String? = null,
|
||||
@field:SerializedName("amount")
|
||||
val amount: Double? = null,
|
||||
@field:SerializedName("rates")
|
||||
var rates: Map<String, Double>? = null,
|
||||
@field:SerializedName("base")
|
||||
val base: String? = null
|
||||
@field:SerializedName("date")
|
||||
val date: String? = null,
|
||||
@field:SerializedName("amount")
|
||||
val amount: Double? = null,
|
||||
@field:SerializedName("rates")
|
||||
var rates: Map<String, Double>? = null,
|
||||
@field:SerializedName("base")
|
||||
val base: String? = null
|
||||
) : CurrencyModelInterface {
|
||||
|
||||
override fun getCurrencyModel(): CurrencyModel {
|
||||
return CurrencyModel(
|
||||
base,
|
||||
rates?.iterator()?.next()?.key,
|
||||
rates?.iterator()?.next()?.value ?: 0.0
|
||||
)
|
||||
base,
|
||||
rates?.iterator()?.next()?.key,
|
||||
rates?.iterator()?.next()?.value ?: 0.0
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package com.appttude.h_mal.easycc.data.repository
|
||||
|
||||
import com.appttude.h_mal.easycc.data.network.response.CurrencyResponse
|
||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
||||
import com.appttude.h_mal.easycc.models.CurrencyModel
|
||||
|
||||
/**
|
||||
* Main entry point for accessing currency data.
|
||||
*/
|
||||
interface Repository {
|
||||
|
||||
suspend fun getDataFromApi(fromCurrency: String, toCurrency: String): ResponseObject
|
||||
|
||||
suspend fun getBackupDataFromApi(fromCurrency: String, toCurrency: String): CurrencyResponse
|
||||
suspend fun getDataFromApi(fromCurrency: String, toCurrency: String): CurrencyModel
|
||||
|
||||
fun getConversionPair(): Pair<String?, String?>
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ import com.appttude.h_mal.easycc.R
|
||||
import com.appttude.h_mal.easycc.data.network.SafeApiRequest
|
||||
import com.appttude.h_mal.easycc.data.network.api.BackupCurrencyApi
|
||||
import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
|
||||
import com.appttude.h_mal.easycc.data.network.response.CurrencyResponse
|
||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
||||
import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.easycc.models.CurrencyModel
|
||||
import com.appttude.h_mal.easycc.utils.convertPairsListToString
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@@ -23,17 +23,19 @@ class RepositoryImpl @Inject constructor(
|
||||
override suspend fun getDataFromApi(
|
||||
fromCurrency: String,
|
||||
toCurrency: String
|
||||
): ResponseObject {
|
||||
// Set currency pairs as correct string for api query eg. AUD_GBP
|
||||
val currencyPair = convertPairsListToString(fromCurrency, toCurrency)
|
||||
return responseUnwrap { api.getCurrencyRate(currencyPair) }
|
||||
}
|
||||
|
||||
override suspend fun getBackupDataFromApi(
|
||||
fromCurrency: String,
|
||||
toCurrency: String
|
||||
): CurrencyResponse {
|
||||
return responseUnwrap { backUpApi.getCurrencyRate(fromCurrency, toCurrency) }
|
||||
): CurrencyModel {
|
||||
return try {
|
||||
// Set currency pairs as correct string for api query eg. AUD_GBP
|
||||
val currencyPair = convertPairsListToString(fromCurrency, toCurrency)
|
||||
responseUnwrap { api.getCurrencyRate(currencyPair) }.getCurrencyModel()
|
||||
} catch (e: IOException) {
|
||||
responseUnwrap {
|
||||
backUpApi.getCurrencyRate(
|
||||
fromCurrency,
|
||||
toCurrency
|
||||
)
|
||||
}.getCurrencyModel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getConversionPair(): Pair<String?, String?> {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.appttude.h_mal.easycc.helper
|
||||
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.models.CurrencyModelInterface
|
||||
import javax.inject.Inject
|
||||
|
||||
class CurrencyDataHelper @Inject constructor(
|
||||
val repository: Repository
|
||||
) {
|
||||
|
||||
suspend fun getDataFromApi(from: String, to: String): CurrencyModelInterface {
|
||||
return try {
|
||||
repository.getDataFromApi(from, to)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
repository.getBackupDataFromApi(from, to)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import com.appttude.h_mal.easycc.utils.trimToThree
|
||||
import javax.inject.Inject
|
||||
|
||||
class WidgetHelper @Inject constructor(
|
||||
private val helper: CurrencyDataHelper,
|
||||
val repository: Repository
|
||||
) {
|
||||
|
||||
@@ -16,7 +15,7 @@ class WidgetHelper @Inject constructor(
|
||||
val s1 = pair.first?.trimToThree() ?: return null
|
||||
val s2 = pair.second?.trimToThree() ?: return null
|
||||
|
||||
return helper.getDataFromApi(s1, s2).getCurrencyModel()
|
||||
return repository.getDataFromApi(s1, s2)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
|
||||
@@ -3,12 +3,12 @@ package com.appttude.h_mal.easycc.models
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class CurrencyObject(
|
||||
@SerializedName("id")
|
||||
var id: String,
|
||||
@SerializedName("fr")
|
||||
var fr: String,
|
||||
@SerializedName("to")
|
||||
var to: String,
|
||||
@SerializedName("val")
|
||||
var value: Double
|
||||
@SerializedName("id")
|
||||
var id: String,
|
||||
@SerializedName("fr")
|
||||
var fr: String,
|
||||
@SerializedName("to")
|
||||
var to: String,
|
||||
@SerializedName("val")
|
||||
var value: Double
|
||||
)
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.appttude.h_mal.easycc.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.appttude.h_mal.easycc.R
|
||||
import com.appttude.h_mal.easycc.utils.*
|
||||
|
||||
abstract class BaseActivity<V : BaseViewModel> : AppCompatActivity() {
|
||||
|
||||
private lateinit var loadingView: View
|
||||
|
||||
abstract val viewModel: V?
|
||||
|
||||
private var loading: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
configureObserver()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a loading view which to be shown during async operations
|
||||
*
|
||||
* #setOnClickListener(null) is an ugly work around to prevent under being clicked during
|
||||
* loading
|
||||
*/
|
||||
private fun instantiateLoadingView() {
|
||||
loadingView = layoutInflater.inflate(R.layout.progress_layout, null)
|
||||
loadingView.setOnClickListener(null)
|
||||
addContentView(loadingView, LayoutParams(MATCH_PARENT, MATCH_PARENT))
|
||||
|
||||
loadingView.hide()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
instantiateLoadingView()
|
||||
}
|
||||
|
||||
fun <A : AppCompatActivity> startActivity(activity: Class<A>) {
|
||||
val intent = Intent(this, activity)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
open fun onStarted() {
|
||||
loadingView.fadeIn()
|
||||
loading = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in case of success or some data emitted from the liveData in viewModel
|
||||
*/
|
||||
open fun onSuccess(data: Any?) {
|
||||
loadingView.fadeOut()
|
||||
loading = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in case of failure or some error emitted from the liveData in viewModel
|
||||
*/
|
||||
open fun onFailure(error: String?) {
|
||||
error?.let { displayToast(it) }
|
||||
loadingView.fadeOut()
|
||||
loading = false
|
||||
}
|
||||
|
||||
private fun configureObserver() {
|
||||
viewModel?.uiState?.observe(this) {
|
||||
when (it) {
|
||||
is ViewState.HasStarted -> onStarted()
|
||||
is ViewState.HasData<*> -> onSuccess(it.data.getContentIfNotHandled())
|
||||
is ViewState.HasError -> onFailure(it.error.getContentIfNotHandled())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun View.fadeIn() = apply {
|
||||
show()
|
||||
triggerAnimation(android.R.anim.fade_in) {}
|
||||
}
|
||||
|
||||
private fun View.fadeOut() = apply {
|
||||
hide()
|
||||
triggerAnimation(android.R.anim.fade_out) {}
|
||||
}
|
||||
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (!loading) super.onBackPressed()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.appttude.h_mal.easycc.ui
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.appttude.h_mal.easycc.utils.Event
|
||||
import com.appttude.h_mal.easycc.utils.ViewState
|
||||
|
||||
abstract class BaseViewModel : ViewModel() {
|
||||
open val uiState: MutableLiveData<ViewState> = MutableLiveData()
|
||||
|
||||
fun onStart() {
|
||||
uiState.postValue(ViewState.HasStarted)
|
||||
}
|
||||
|
||||
fun <T : Any> onSuccess(result: T) {
|
||||
uiState.postValue(ViewState.HasData(Event(result)))
|
||||
}
|
||||
|
||||
protected fun onError(error: String) {
|
||||
uiState.postValue(ViewState.HasError(Event(error)))
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import android.view.WindowManager
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import com.appttude.h_mal.easycc.R
|
||||
|
||||
/**
|
||||
@@ -18,7 +17,7 @@ import com.appttude.h_mal.easycc.R
|
||||
@Suppress("DEPRECATION")
|
||||
class CustomDialogClass(
|
||||
context: Context,
|
||||
private val clickListener: ClickListener
|
||||
val onSelect: (String) -> Unit
|
||||
) : Dialog(context) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -30,7 +29,6 @@ class CustomDialogClass(
|
||||
// Keyboard not to overlap dialog
|
||||
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
|
||||
// array adapter for list of currencies in R.Strings
|
||||
val arrayAdapter =
|
||||
ArrayAdapter.createFromResource(
|
||||
context, R.array.currency_arrays,
|
||||
@@ -52,13 +50,8 @@ class CustomDialogClass(
|
||||
|
||||
// interface selection back to calling activity
|
||||
list_view.setOnItemClickListener { adapterView, _, i, _ ->
|
||||
clickListener.onText(adapterView.getItemAtPosition(i).toString())
|
||||
onSelect.invoke(adapterView.getItemAtPosition(i).toString())
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interface to handle selection within dialog
|
||||
interface ClickListener {
|
||||
fun onText(currencyName: String)
|
||||
}
|
||||
@@ -7,18 +7,16 @@ import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.appttude.h_mal.easycc.R
|
||||
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding
|
||||
import com.appttude.h_mal.easycc.models.CurrencyModel
|
||||
import com.appttude.h_mal.easycc.ui.BaseActivity
|
||||
import com.appttude.h_mal.easycc.utils.clearEditText
|
||||
import com.appttude.h_mal.easycc.utils.displayToast
|
||||
import com.appttude.h_mal.easycc.utils.hideView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
class MainActivity : BaseActivity<MainViewModel>(), View.OnClickListener {
|
||||
|
||||
private val viewModel: MainViewModel by viewModels()
|
||||
override val viewModel: MainViewModel by viewModels()
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -29,34 +27,22 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
* Prevent keyboard overlapping views
|
||||
*/
|
||||
window.setSoftInputMode(
|
||||
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN or
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
|
||||
)
|
||||
|
||||
viewModel.initiate(intent.extras)
|
||||
|
||||
binding.currencyOne.text = viewModel.rateIdTo
|
||||
binding.currencyTwo.text = viewModel.rateIdFrom
|
||||
binding.currencyOne.text = viewModel.rateIdFrom
|
||||
binding.currencyTwo.text = viewModel.rateIdTo
|
||||
|
||||
setUpListeners()
|
||||
setUpObservers()
|
||||
}
|
||||
|
||||
private fun setUpObservers() {
|
||||
viewModel.operationStartedListener.observe(this) {
|
||||
binding.progressBar.hideView(false)
|
||||
}
|
||||
viewModel.operationFinishedListener.observe(this) { pair ->
|
||||
// hide progress bar
|
||||
binding.progressBar.hideView(true)
|
||||
if (pair.first) {
|
||||
// Operation was successful remove text in EditTexts
|
||||
binding.bottomInsertValues.clearEditText()
|
||||
binding.topInsertValue.clearEditText()
|
||||
} else {
|
||||
// Display Toast with error message returned from Viewmodel
|
||||
pair.second?.let { displayToast(it) }
|
||||
}
|
||||
override fun onSuccess(data: Any?) {
|
||||
super.onSuccess(data)
|
||||
if (data is CurrencyModel) {
|
||||
binding.bottomInsertValues.clearEditText()
|
||||
binding.topInsertValue.clearEditText()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,25 +55,21 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
}
|
||||
|
||||
private fun showCustomDialog(view: View?) {
|
||||
CustomDialogClass(this, object : ClickListener {
|
||||
override fun onText(currencyName: String) {
|
||||
(view as TextView).text = currencyName
|
||||
viewModel.setCurrencyName(view.tag, currencyName)
|
||||
}
|
||||
|
||||
}).show()
|
||||
CustomDialogClass(this) {
|
||||
(view as TextView).text = it
|
||||
viewModel.setCurrencyName(view.tag, it)
|
||||
}.show()
|
||||
}
|
||||
|
||||
override fun onClick(view: View?) {
|
||||
showCustomDialog(view)
|
||||
}
|
||||
|
||||
// Text watcher applied to EditText @topInsertValue
|
||||
private val textWatcherClass: TextWatcher = object : TextWatcher {
|
||||
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
||||
// Remove text watcher on other text watcher to prevent infinite loop
|
||||
binding.bottomInsertValues.removeTextChangedListener(textWatcherClass2)
|
||||
// Clear any values if current EditText is empty
|
||||
|
||||
if (binding.topInsertValue.text.isNullOrEmpty())
|
||||
binding.bottomInsertValues.setText("")
|
||||
}
|
||||
@@ -102,7 +84,6 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
|
||||
private val textWatcherClass2: TextWatcher = object : TextWatcher {
|
||||
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
||||
|
||||
binding.topInsertValue.removeTextChangedListener(textWatcherClass)
|
||||
if (binding.bottomInsertValues.text.isNullOrEmpty())
|
||||
binding.topInsertValue.clearEditText()
|
||||
@@ -115,4 +96,5 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
package com.appttude.h_mal.easycc.ui.main
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
|
||||
import com.appttude.h_mal.easycc.ui.BaseViewModel
|
||||
import com.appttude.h_mal.easycc.utils.toTwoDpString
|
||||
import com.appttude.h_mal.easycc.utils.trimToThree
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
@@ -21,60 +16,48 @@ import javax.inject.Inject
|
||||
*/
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
private val currencyDataHelper: CurrencyDataHelper,
|
||||
private val repository: Repository
|
||||
) : ViewModel() {
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val conversionPairs by lazy { repository.getConversionPair() }
|
||||
|
||||
// Viewbinding to textviews in @activity_main.xml
|
||||
// Viewbinded variables
|
||||
var rateIdFrom: String? = null
|
||||
var rateIdTo: String? = null
|
||||
|
||||
//operation results livedata based on outcome of operation
|
||||
val operationStartedListener = MutableLiveData<Boolean>()
|
||||
val operationFinishedListener = MutableLiveData<Pair<Boolean, String?>>()
|
||||
|
||||
private var conversionRate: Double = 1.00
|
||||
|
||||
private fun getExchangeRate() {
|
||||
operationStartedListener.postValue(true)
|
||||
|
||||
// view binded exchange rates selected null checked
|
||||
onStart()
|
||||
if (rateIdFrom.isNullOrEmpty() || rateIdTo.isNullOrEmpty()) {
|
||||
operationFinishedListener.postValue(Pair(false, "Select currencies"))
|
||||
onError("Select both currencies")
|
||||
return
|
||||
}
|
||||
|
||||
// No need to call api as it will return exchange rate as 1
|
||||
if (rateIdFrom == rateIdTo) {
|
||||
conversionRate = 1.00
|
||||
operationFinishedListener.postValue(Pair(true, null))
|
||||
onError("Currency selections are the same")
|
||||
return
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
// Non-null assertion (!!) as values have been null checked and have not changed
|
||||
val exchangeResponse = currencyDataHelper.getDataFromApi(
|
||||
val exchangeResponse = repository.getDataFromApi(
|
||||
rateIdFrom!!.trimToThree(),
|
||||
rateIdTo!!.trimToThree()
|
||||
)
|
||||
|
||||
exchangeResponse.getCurrencyModel().let {
|
||||
exchangeResponse.let {
|
||||
conversionRate = it.rate
|
||||
repository.setConversionPair(rateIdFrom!!, rateIdTo!!)
|
||||
|
||||
operationFinishedListener.postValue(Pair(true, null))
|
||||
onSuccess(it)
|
||||
return@launch
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.message?.let {
|
||||
operationFinishedListener.postValue(Pair(false, it))
|
||||
return@launch
|
||||
}
|
||||
e.message?.let { onError(it) }
|
||||
}
|
||||
operationFinishedListener.postValue(Pair(false, "Failed to retrieve rate"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,9 @@ import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.appttude.h_mal.easycc.databinding.CurrencyAppWidgetConfigureBinding
|
||||
import com.appttude.h_mal.easycc.ui.main.ClickListener
|
||||
import com.appttude.h_mal.easycc.ui.BaseActivity
|
||||
import com.appttude.h_mal.easycc.ui.main.CustomDialogClass
|
||||
import com.appttude.h_mal.easycc.utils.displayToast
|
||||
import com.appttude.h_mal.easycc.utils.transformIntToArray
|
||||
import com.appttude.h_mal.easycc.widget.CurrencyAppWidgetKotlin
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -20,16 +18,16 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
* The configuration screen for the [CurrencyAppWidgetKotlin] AppWidget.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(),
|
||||
class CurrencyAppWidgetConfigureActivityKotlin : BaseActivity<WidgetViewModel>(),
|
||||
View.OnClickListener {
|
||||
|
||||
val viewModel: WidgetViewModel by viewModels()
|
||||
override val viewModel: WidgetViewModel by viewModels()
|
||||
|
||||
private lateinit var binding: CurrencyAppWidgetConfigureBinding
|
||||
private var mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
|
||||
public override fun onCreate(icicle: Bundle?) {
|
||||
super.onCreate(icicle)
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = CurrencyAppWidgetConfigureBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
@@ -50,10 +48,8 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(),
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.initiate(mAppWidgetId)
|
||||
|
||||
setupObserver()
|
||||
setupClickListener()
|
||||
}
|
||||
|
||||
@@ -63,17 +59,9 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(),
|
||||
binding.currencyTwo.setOnClickListener(this)
|
||||
}
|
||||
|
||||
private fun setupObserver() {
|
||||
viewModel.operationFinishedListener.observe(this) {
|
||||
|
||||
// it.first is a the success of the operation
|
||||
if (it.first) {
|
||||
displaySubmitDialog()
|
||||
} else {
|
||||
// failed operation - display toast with message from it.second
|
||||
it.second?.let { message -> displayToast(message) }
|
||||
}
|
||||
}
|
||||
override fun onSuccess(data: Any?) {
|
||||
super.onSuccess(data)
|
||||
displaySubmitDialog()
|
||||
}
|
||||
|
||||
override fun onClick(view: View?) {
|
||||
@@ -98,12 +86,10 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(),
|
||||
|
||||
|
||||
private fun showCustomDialog(view: View?) {
|
||||
CustomDialogClass(this, object : ClickListener {
|
||||
override fun onText(currencyName: String) {
|
||||
(view as TextView).text = currencyName
|
||||
viewModel.setCurrencyName(view.tag, currencyName)
|
||||
}
|
||||
}).show()
|
||||
CustomDialogClass(this) {
|
||||
(view as TextView).text = it
|
||||
viewModel.setCurrencyName(view.tag, it)
|
||||
}.show()
|
||||
}
|
||||
|
||||
fun finishCurrencyWidgetActivity() {
|
||||
|
||||
@@ -3,8 +3,6 @@ package com.appttude.h_mal.easycc.ui.widget
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import com.appttude.h_mal.easycc.R
|
||||
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding
|
||||
import com.appttude.h_mal.easycc.databinding.ConfirmDialogBinding
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.appttude.h_mal.easycc.ui.widget
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||
import com.appttude.h_mal.easycc.ui.BaseViewModel
|
||||
import com.appttude.h_mal.easycc.utils.trimToThree
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
@@ -10,7 +9,7 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
class WidgetViewModel @Inject constructor(
|
||||
private val repository: Repository
|
||||
) : ViewModel() {
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val defaultCurrency: String by lazy { repository.getCurrenciesList()[0] }
|
||||
var appWidgetId: Int? = null
|
||||
@@ -20,7 +19,6 @@ class WidgetViewModel @Inject constructor(
|
||||
var rateIdTo: String? = null
|
||||
|
||||
// Live data to feedback to @CurrencyAppWidgetConfigureActivityKotlin
|
||||
val operationFinishedListener = MutableLiveData<Pair<Boolean, String?>>()
|
||||
|
||||
// Setup viewmodel app widget ID
|
||||
// Set default values for text views
|
||||
@@ -43,15 +41,14 @@ class WidgetViewModel @Inject constructor(
|
||||
|
||||
fun submitSelectionOnClick() {
|
||||
if (rateIdTo == null || rateIdFrom == null) {
|
||||
operationFinishedListener.value = Pair(false, "Selections incomplete")
|
||||
onError("Selections incomplete")
|
||||
return
|
||||
}
|
||||
if (rateIdFrom == rateIdTo) {
|
||||
operationFinishedListener.value =
|
||||
Pair(false, "Selected rates cannot be the same ${rateIdFrom}${rateIdTo}")
|
||||
onError("Selected rates cannot be the same ${rateIdFrom}${rateIdTo}")
|
||||
return
|
||||
}
|
||||
operationFinishedListener.value = Pair(true, null)
|
||||
onSuccess(Unit)
|
||||
}
|
||||
|
||||
fun setWidgetStored() {
|
||||
|
||||
24
app/src/main/java/com/appttude/h_mal/easycc/utils/Event.kt
Normal file
24
app/src/main/java/com/appttude/h_mal/easycc/utils/Event.kt
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.appttude.h_mal.easycc.utils
|
||||
|
||||
open class Event<out T>(private val content: T) {
|
||||
|
||||
var hasBeenHandled = false
|
||||
private set // Allow external read but not write
|
||||
|
||||
/**
|
||||
* Returns the content and prevents its use again.
|
||||
*/
|
||||
fun getContentIfNotHandled(): T? {
|
||||
return if (hasBeenHandled) {
|
||||
null
|
||||
} else {
|
||||
hasBeenHandled = true
|
||||
content
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content, even if it's already been handled.
|
||||
*/
|
||||
fun peekContent(): T = content
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.appttude.h_mal.easycc.utils
|
||||
|
||||
sealed class ViewState {
|
||||
object HasStarted : ViewState()
|
||||
class HasData<T : Any>(val data: Event<T>) : ViewState()
|
||||
class HasError(val error: Event<String>) : ViewState()
|
||||
}
|
||||
@@ -2,21 +2,34 @@ package com.appttude.h_mal.easycc.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.AnimRes
|
||||
|
||||
fun EditText.clearEditText() {
|
||||
this.setText("")
|
||||
}
|
||||
|
||||
fun View.hideView(vis: Boolean) {
|
||||
visibility = if (vis) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.displayToast(message: String) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun View.triggerAnimation(@AnimRes id: Int, complete: (View) -> Unit) {
|
||||
val animation = AnimationUtils.loadAnimation(context, id)
|
||||
animation.setAnimationListener(object : Animation.AnimationListener {
|
||||
override fun onAnimationEnd(animation: Animation?) = complete(this@triggerAnimation)
|
||||
override fun onAnimationStart(a: Animation?) {}
|
||||
override fun onAnimationRepeat(a: Animation?) {}
|
||||
})
|
||||
startAnimation(animation)
|
||||
}
|
||||
|
||||
fun View.show() {
|
||||
this.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
fun View.hide() {
|
||||
this.visibility = View.GONE
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="192dp"
|
||||
android:width="1080dp"
|
||||
android:height="1920dp"
|
||||
android:viewportWidth="1080"
|
||||
android:viewportHeight="1920">
|
||||
<path android:pathData="M0,0L1080,0L1080,1920L0,1920L0,0Z">
|
||||
@@ -21,4 +21,4 @@
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
||||
</vector>
|
||||
@@ -99,17 +99,4 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colour_four"
|
||||
android:indeterminateTintMode="src_atop"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
18
app/src/main/res/layout/progress_layout.xml
Normal file
18
app/src/main/res/layout/progress_layout.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#4D000000">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:layout_gravity="center"
|
||||
android:elevation="0.2dp"
|
||||
android:indeterminateTint="@color/colour_four"
|
||||
android:indeterminateTintMode="src_atop"/>
|
||||
|
||||
</FrameLayout>
|
||||
Reference in New Issue
Block a user