- lint checks done

- added service intent

Took 1 hour 7 minutes
This commit is contained in:
2021-06-12 22:50:52 +01:00
parent 33f1738d1e
commit 9160551cb4
35 changed files with 322 additions and 365 deletions

Binary file not shown.

View File

@@ -22,7 +22,7 @@ import org.kodein.di.generic.singleton
class AppClass : Application(), KodeinAware { class AppClass : Application(), KodeinAware {
// Kodein Dependecy Injection singletons and providers created // KODEIN DI components declaration
override val kodein by Kodein.lazy { override val kodein by Kodein.lazy {
import(androidXModule(this@AppClass)) import(androidXModule(this@AppClass))

View File

@@ -1,6 +1,5 @@
package com.appttude.h_mal.easycc.data.network package com.appttude.h_mal.easycc.data.network
import android.util.Log
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import retrofit2.Response import retrofit2.Response
@@ -10,7 +9,6 @@ import java.io.IOException
* This abstract class extract objects from Retrofit [Response] * This abstract class extract objects from Retrofit [Response]
* or throws IOException if object does not exist * or throws IOException if object does not exist
*/ */
private const val TAG = "SafeApiRequest"
abstract class SafeApiRequest { abstract class SafeApiRequest {
suspend fun <T : Any> responseUnwrap( suspend fun <T : Any> responseUnwrap(
@@ -45,10 +43,6 @@ abstract class SafeApiRequest {
} }
print(log) print(log)
// Log.e("Api Response Error", log)
//return error message
//if null return error code
return errorMessageString ?: errorCode return errorMessageString ?: errorCode
} }

View File

@@ -11,18 +11,16 @@ import retrofit2.http.GET
import retrofit2.http.Query import retrofit2.http.Query
/** /**
* Retrofit2 Network class to create network requests * Retrofit Network class to currency api calls
*/ */
interface BackupCurrencyApi { interface BackupCurrencyApi {
// Get rate from server with arguments passed in Repository
@GET("latest?") @GET("latest?")
suspend fun getCurrencyRate( suspend fun getCurrencyRate(
@Query("from") currencyFrom: String, @Query("from") currencyFrom: String,
@Query("to") currencyTo: String @Query("to") currencyTo: String
): Response<CurrencyResponse> ): Response<CurrencyResponse>
// interface invokation to be used in application class
companion object { companion object {
operator fun invoke( operator fun invoke(
networkConnectionInterceptor: NetworkConnectionInterceptor, networkConnectionInterceptor: NetworkConnectionInterceptor,
@@ -34,7 +32,6 @@ interface BackupCurrencyApi {
.addNetworkInterceptor(networkConnectionInterceptor) .addNetworkInterceptor(networkConnectionInterceptor)
.build() .build()
// Build retrofit
return Retrofit.Builder() return Retrofit.Builder()
.client(okkHttpclient) .client(okkHttpclient)
.baseUrl("https://api.frankfurter.app/") .baseUrl("https://api.frankfurter.app/")
@@ -44,6 +41,4 @@ interface BackupCurrencyApi {
} }
} }
} }

View File

@@ -12,7 +12,7 @@ import retrofit2.http.GET
import retrofit2.http.Query import retrofit2.http.Query
/** /**
* Retrofit2 Network class to create network requests * Retrofit Network class to currency api calls
*/ */
interface CurrencyApi { interface CurrencyApi {
@@ -28,14 +28,12 @@ interface CurrencyApi {
interceptor: HttpLoggingInterceptor interceptor: HttpLoggingInterceptor
): CurrencyApi { ): CurrencyApi {
// okkHttpclient with injected interceptors
val okkHttpclient = OkHttpClient.Builder() val okkHttpclient = OkHttpClient.Builder()
.addInterceptor(interceptor) .addInterceptor(interceptor)
.addInterceptor(queryInterceptor) .addInterceptor(queryInterceptor)
.addNetworkInterceptor(networkConnectionInterceptor) .addNetworkInterceptor(networkConnectionInterceptor)
.build() .build()
// Build retrofit
return Retrofit.Builder() return Retrofit.Builder()
.client(okkHttpclient) .client(okkHttpclient)
.baseUrl("https://free.currencyconverterapi.com/api/v3/") .baseUrl("https://free.currencyconverterapi.com/api/v3/")

View File

@@ -3,14 +3,17 @@ package com.appttude.h_mal.easycc.data.network.interceptors
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.os.Build
import androidx.annotation.RequiresApi
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import java.io.IOException import java.io.IOException
/** /**
* Interceptor used in [CurrencyApi] to intercept network status * Interceptor used in network classes to check network status
* *
*/ */
@Suppress("DEPRECATION")
class NetworkConnectionInterceptor( class NetworkConnectionInterceptor(
context: Context context: Context
) : Interceptor { ) : Interceptor {
@@ -29,6 +32,7 @@ class NetworkConnectionInterceptor(
val connectivityManager = val connectivityManager =
applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
connectivityManager?.let { connectivityManager?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply { it.getNetworkCapabilities(connectivityManager.activeNetwork)?.apply {
result = when { result = when {
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
@@ -36,6 +40,16 @@ class NetworkConnectionInterceptor(
else -> false else -> false
} }
} }
} else {
it.activeNetworkInfo?.run {
result = when (type) {
ConnectivityManager.TYPE_WIFI -> true
ConnectivityManager.TYPE_MOBILE -> true
ConnectivityManager.TYPE_ETHERNET -> true
else -> false
}
}
}
} }
return result return result
} }

View File

@@ -1,7 +1,6 @@
package com.appttude.h_mal.easycc.data.network.interceptors package com.appttude.h_mal.easycc.data.network.interceptors
import android.content.Context import android.content.Context
import com.appttude.h_mal.easycc.BuildConfig
import com.appttude.h_mal.easycc.R import com.appttude.h_mal.easycc.R
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Interceptor import okhttp3.Interceptor

View File

@@ -18,8 +18,8 @@ class PreferenceProvider(context: Context) {
private val appContext = context.applicationContext private val appContext = context.applicationContext
// Instance of Shared preferences // Instance of Shared preferences
private val preference: SharedPreferences private val preference: SharedPreferences =
= PreferenceManager.getDefaultSharedPreferences(appContext) PreferenceManager.getDefaultSharedPreferences(appContext)
// Lazy declaration of default rate if no rate is retrieved from // Lazy declaration of default rate if no rate is retrieved from
private val defaultRate: String by lazy { private val defaultRate: String by lazy {
@@ -50,8 +50,10 @@ class PreferenceProvider(context: Context) {
} }
// Save currency pairs for widget // Save currency pairs for widget
fun saveWidgetConversionPair(fromString: String, fun saveWidgetConversionPair(
toString: String, appWidgetId: Int) { fromString: String,
toString: String, appWidgetId: Int
) {
preference.edit() preference.edit()
.putString("${appWidgetId}_$CURRENCY_ONE", fromString) .putString("${appWidgetId}_$CURRENCY_ONE", fromString)
@@ -68,7 +70,8 @@ class PreferenceProvider(context: Context) {
} }
private fun getWidgetConversionString( private fun getWidgetConversionString(
appWidgetId: Int, conversionName: String): String? { appWidgetId: Int, conversionName: String
): String? {
return preference return preference
.getString("${appWidgetId}_$conversionName", defaultRate) .getString("${appWidgetId}_$conversionName", defaultRate)
} }
@@ -78,7 +81,6 @@ class PreferenceProvider(context: Context) {
.remove("${id}_$CURRENCY_ONE") .remove("${id}_$CURRENCY_ONE")
.remove("${id}_$CURRENCY_TWO") .remove("${id}_$CURRENCY_TWO")
.apply() .apply()
} }
} }

View File

@@ -49,8 +49,10 @@ class RepositoryImpl (
override fun getWidgetConversionPairs(appWidgetId: Int): Pair<String?, String?> = override fun getWidgetConversionPairs(appWidgetId: Int): Pair<String?, String?> =
prefs.getWidgetConversionPair(appWidgetId) prefs.getWidgetConversionPair(appWidgetId)
override fun setWidgetConversionPairs(fromCurrency: String, override fun setWidgetConversionPairs(
toCurrency: String, appWidgetId: Int) { fromCurrency: String,
toCurrency: String, appWidgetId: Int
) {
return prefs.saveWidgetConversionPair(fromCurrency, toCurrency, appWidgetId) return prefs.saveWidgetConversionPair(fromCurrency, toCurrency, appWidgetId)
} }

View File

@@ -2,7 +2,6 @@ package com.appttude.h_mal.easycc.helper
import com.appttude.h_mal.easycc.data.repository.Repository import com.appttude.h_mal.easycc.data.repository.Repository
import com.appttude.h_mal.easycc.models.CurrencyModelInterface import com.appttude.h_mal.easycc.models.CurrencyModelInterface
import java.lang.Exception
class CurrencyDataHelper( class CurrencyDataHelper(
val repository: Repository val repository: Repository

View File

@@ -4,10 +4,8 @@ import com.appttude.h_mal.easycc.data.repository.Repository
import com.appttude.h_mal.easycc.models.CurrencyModel import com.appttude.h_mal.easycc.models.CurrencyModel
import com.appttude.h_mal.easycc.utils.trimToThree import com.appttude.h_mal.easycc.utils.trimToThree
import kotlin.Exception
class WidgetHelper( class WidgetHelper(
val helper: CurrencyDataHelper, private val helper: CurrencyDataHelper,
val repository: Repository val repository: Repository
) { ) {

View File

@@ -13,6 +13,7 @@ import kotlinx.android.synthetic.main.custom_dialog.*
/** /**
* Custom dialog when selecting currencies from list with filter * Custom dialog when selecting currencies from list with filter
*/ */
@Suppress("DEPRECATION")
class CustomDialogClass( class CustomDialogClass(
context: Context, context: Context,
private val clickListener: ClickListener private val clickListener: ClickListener
@@ -31,7 +32,8 @@ class CustomDialogClass(
val arrayAdapter = val arrayAdapter =
ArrayAdapter.createFromResource( ArrayAdapter.createFromResource(
context, R.array.currency_arrays, context, R.array.currency_arrays,
android.R.layout.simple_list_item_1) android.R.layout.simple_list_item_1
)
list_view.adapter = arrayAdapter list_view.adapter = arrayAdapter
@@ -41,6 +43,7 @@ class CustomDialogClass(
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
arrayAdapter.filter.filter(charSequence) arrayAdapter.filter.filter(charSequence)
} }
override fun afterTextChanged(editable: Editable) {} override fun afterTextChanged(editable: Editable) {}
}) })

View File

@@ -20,6 +20,7 @@ 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
@Suppress("DEPRECATION")
class MainActivity : AppCompatActivity(), KodeinAware, View.OnClickListener { class MainActivity : AppCompatActivity(), KodeinAware, View.OnClickListener {
// Retrieve MainViewModelFactory via dependency injection // Retrieve MainViewModelFactory via dependency injection
@@ -54,10 +55,10 @@ class MainActivity : AppCompatActivity(), KodeinAware, View.OnClickListener {
} }
private fun setUpObservers() { private fun setUpObservers() {
viewModel.operationStartedListener.observe(this, Observer { viewModel.operationStartedListener.observe(this, {
progressBar.hideView(false) progressBar.hideView(false)
}) })
viewModel.operationFinishedListener.observe(this, Observer { pair -> viewModel.operationFinishedListener.observe(this, { pair ->
// hide progress bar // hide progress bar
progressBar.hideView(true) progressBar.hideView(true)
if (pair.first) { if (pair.first) {

View File

@@ -98,7 +98,9 @@ class MainViewModel(
when (tag.toString()) { when (tag.toString()) {
"top" -> rateIdFrom = currencyName "top" -> rateIdFrom = currencyName
"bottom" -> rateIdTo = currencyName "bottom" -> rateIdTo = currencyName
else -> { return } else -> {
return
}
} }
getExchangeRate() getExchangeRate()

View File

@@ -25,7 +25,8 @@ import org.kodein.di.generic.instance
/** /**
* The configuration screen for the [CurrencyAppWidgetKotlin] AppWidget. * The configuration screen for the [CurrencyAppWidgetKotlin] AppWidget.
*/ */
class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(), KodeinAware, View.OnClickListener { class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(), KodeinAware,
View.OnClickListener {
override val kodein by kodein() override val kodein by kodein()
private val factory: WidgetViewModelFactory by instance() private val factory: WidgetViewModelFactory by instance()
@@ -47,7 +48,8 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(), KodeinAwar
val extras = intent.extras val extras = intent.extras
if (extras != null) { if (extras != null) {
mAppWidgetId = extras.getInt( mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID
)
} }
// If this activity was started with an intent without an app widget ID, finish with an error. // If this activity was started with an intent without an app widget ID, finish with an error.
@@ -72,7 +74,7 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(), KodeinAwar
} }
private fun setupObserver() { private fun setupObserver() {
viewModel.operationFinishedListener.observe(this, Observer { viewModel.operationFinishedListener.observe(this, {
// it.first is a the success of the operation // it.first is a the success of the operation
if (it.first) { if (it.first) {
@@ -136,7 +138,8 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(), KodeinAwar
fun sendUpdateIntent() { fun sendUpdateIntent() {
// It is the responsibility of the configuration activity to update the app widget // It is the responsibility of the configuration activity to update the app widget
// Send update broadcast to widget app class // Send update broadcast to widget app class
Intent(this@CurrencyAppWidgetConfigureActivityKotlin, Intent(
this@CurrencyAppWidgetConfigureActivityKotlin,
CurrencyAppWidgetKotlin::class.java CurrencyAppWidgetKotlin::class.java
).apply { ).apply {
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE action = AppWidgetManager.ACTION_APPWIDGET_UPDATE

View File

@@ -1,48 +0,0 @@
package com.appttude.h_mal.easycc.ui.widget
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.WindowManager
import android.widget.ArrayAdapter
import com.appttude.h_mal.easycc.R
import kotlinx.android.synthetic.main.custom_dialog.*
/*
widget for when submitting the completed selections
*/
class WidgetItemSelectDialog(
context: Context,
private val dialogResult: DialogResult
) :Dialog(context){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.custom_dialog)
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)
list_view.adapter = arrayAdapter
search_text.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
arrayAdapter.filter.filter(charSequence)
}
override fun afterTextChanged(editable: Editable) {}
})
list_view.setOnItemClickListener{ adapterView, _, i, _ ->
dialogResult.result(adapterView.getItemAtPosition(i).toString())
dismiss()
}
}
}
interface DialogResult{
fun result(result : String)
}

View File

@@ -23,8 +23,7 @@ class WidgetViewModel(
// Set default values for text views // Set default values for text views
fun initiate(appId: Int) { fun initiate(appId: Int) {
appWidgetId = appId appWidgetId = appId
val widgetString val widgetString = repository.getWidgetConversionPairs(appId)
= repository.getWidgetConversionPairs(appId)
rateIdFrom = widgetString.first ?: defaultCurrency rateIdFrom = widgetString.first ?: defaultCurrency
rateIdTo = widgetString.second ?: defaultCurrency rateIdTo = widgetString.second ?: defaultCurrency
@@ -61,7 +60,9 @@ class WidgetViewModel(
when (tag.toString()) { when (tag.toString()) {
"top" -> rateIdFrom = currencyName "top" -> rateIdFrom = currencyName
"bottom" -> rateIdTo = currencyName "bottom" -> rateIdTo = currencyName
else -> { return } else -> {
return
}
} }
} }

View File

@@ -2,7 +2,6 @@ package com.appttude.h_mal.easycc.utils
import java.lang.Double.valueOf import java.lang.Double.valueOf
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.*
fun transformIntToArray(int: Int): IntArray{ fun transformIntToArray(int: Int): IntArray{
return intArrayOf(int) return intArrayOf(int)

View File

@@ -10,7 +10,11 @@ fun EditText.clearEditText(){
} }
fun View.hideView(vis: Boolean) { fun View.hideView(vis: Boolean) {
visibility = if (vis){ View.GONE } else { View.VISIBLE } visibility = if (vis) {
View.GONE
} else {
View.VISIBLE
}
} }
fun Context.displayToast(message: String) { fun Context.displayToast(message: String) {

View File

@@ -1,21 +1,11 @@
package com.appttude.h_mal.easycc.widget package com.appttude.h_mal.easycc.widget
import android.app.Activity
import android.app.PendingIntent
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
import android.widget.RemoteViews
import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.helper.WidgetHelper import com.appttude.h_mal.easycc.helper.WidgetHelper
import com.appttude.h_mal.easycc.ui.main.MainActivity import com.appttude.h_mal.easycc.widget.WidgetServiceIntent.Companion.enqueueWork
import com.appttude.h_mal.easycc.utils.transformIntToArray
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.LateInitKodein import org.kodein.di.LateInitKodein
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
@@ -23,9 +13,8 @@ import org.kodein.di.generic.instance
/** /**
* Implementation of App Widget functionality. * Implementation of App Widget functionality.
* App Widget Configuration implemented in [CurrencyAppWidgetConfigureActivityKotlin] * App Widget Configuration implemented in [CurrencyAppWidgetKotlin]
*/ */
private const val TAG = "CurrencyAppWidgetKotlin"
class CurrencyAppWidgetKotlin : AppWidgetProvider() { class CurrencyAppWidgetKotlin : AppWidgetProvider() {
@@ -39,12 +28,7 @@ class CurrencyAppWidgetKotlin : AppWidgetProvider() {
appWidgetManager: AppWidgetManager, appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray appWidgetIds: IntArray
) { ) {
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein loadWidget(context)
Log.i(TAG, "onUpdate() appWidgetIds = ${appWidgetIds.size}")
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
super.onUpdate(context, appWidgetManager, appWidgetIds) super.onUpdate(context, appWidgetManager, appWidgetIds)
} }
@@ -58,113 +42,13 @@ class CurrencyAppWidgetKotlin : AppWidgetProvider() {
} }
override fun onEnabled(context: Context) { override fun onEnabled(context: Context) {
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein loadWidget(context)
// Enter relevant functionality for when the first widget is created
AppWidgetManager.getInstance(context).apply {
val thisAppWidget =
ComponentName(context.packageName, CurrencyAppWidgetKotlin::class.java.name)
val appWidgetIds = getAppWidgetIds(thisAppWidget)
onUpdate(context, this, appWidgetIds)
}
super.onEnabled(context) super.onEnabled(context)
} }
override fun onDisabled(context: Context) { private fun loadWidget(context: Context) {
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein val mIntent = Intent(context, CurrencyAppWidgetKotlin::class.java)
// Enter relevant functionality for when the last widget is disabled enqueueWork(context, mIntent)
}
private fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
// Construct the RemoteViews object
val views = RemoteViews(context.packageName, R.layout.currency_app_widget)
CoroutineScope(Dispatchers.Main).launch {
val exchangeResponse = repository.getWidgetData()
exchangeResponse?.let {
val titleString = "${it.from}${it.to}"
views.setTextViewText(R.id.exchangeName, titleString)
views.setTextViewText(R.id.exchangeRate, it.rate.toString())
setUpdateIntent(context, appWidgetId).let { intent ->
//set the pending intent to the icon
views.setImageViewResource(R.id.refresh_icon, R.drawable.ic_refresh_white_24dp)
views.setOnClickPendingIntent(R.id.refresh_icon, intent)
}
val clickIntentTemplate = clickingIntent(context)
val configPendingIntent =
PendingIntent.getActivity(
context, appWidgetId, clickIntentTemplate,
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.widget_view, configPendingIntent)
}
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
private fun setUpdateIntent(context: Context, appWidgetId: Int): PendingIntent? {
//Create update intent for refresh icon
val updateIntent = Intent(
context, CurrencyAppWidgetKotlin::class.java
).apply {
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, transformIntToArray(appWidgetId))
}
//add previous intent to this pending intent
return PendingIntent.getBroadcast(
context,
appWidgetId,
updateIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
private fun clickingIntent(
context: Context
): Intent {
val pair = repository.repository.getConversionPair()
val s1 = pair.first
val s2 = pair.second
return Intent(context, MainActivity::class.java).apply {
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
putExtra("parse_1", s1)
putExtra("parse_2", s2)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
}
private fun <T: Activity> clickingIntent(
context: Context,
activity: Class<T>,
vararg argPairs: Pair<String, Any?>
): Intent {
return Intent(context, activity::class.java).apply {
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
argPairs.forEach {
putExtra(it.first, it.second)
}
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
}
private fun <T: Any> Intent.putExtra(s: String, second: T?) {
when(second){
is String -> putExtra(s,second)
}
} }
} }

View File

@@ -0,0 +1,121 @@
package com.appttude.h_mal.easycc.widget
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import androidx.core.app.JobIntentService
import com.appttude.h_mal.easycc.R
import com.appttude.h_mal.easycc.helper.WidgetHelper
import com.appttude.h_mal.easycc.ui.main.MainActivity
import com.appttude.h_mal.easycc.utils.transformIntToArray
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.LateInitKodein
import org.kodein.di.generic.instance
class WidgetServiceIntent : JobIntentService() {
//DI with kodein to use in CurrencyAppWidgetKotlin
private val kodein = LateInitKodein()
private val repository: WidgetHelper by kodein.instance()
override fun onHandleWork(intent: Intent) {
kodein.baseKodein = this.kodein
val appWidgetManager = AppWidgetManager.getInstance(this)
val thisAppWidget = ComponentName(packageName, CurrencyAppWidgetKotlin::class.java.name)
val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget)
for (appWidgetId in appWidgetIds) {
updateAppWidget(this, appWidgetManager, appWidgetId)
}
}
private fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
// Construct the RemoteViews object
val views = RemoteViews(context.packageName, R.layout.currency_app_widget)
CoroutineScope(Dispatchers.Main).launch {
val exchangeResponse = repository.getWidgetData()
exchangeResponse?.let {
val titleString = "${it.from}${it.to}"
views.setTextViewText(R.id.exchangeName, titleString)
views.setTextViewText(R.id.exchangeRate, it.rate.toString())
setUpdateIntent(context, appWidgetId).let { intent ->
//set the pending intent to the icon
views.setImageViewResource(R.id.refresh_icon, R.drawable.ic_refresh_white_24dp)
views.setOnClickPendingIntent(R.id.refresh_icon, intent)
}
val clickIntentTemplate = clickingIntent(context)
val configPendingIntent =
PendingIntent.getActivity(
context, appWidgetId, clickIntentTemplate,
PendingIntent.FLAG_UPDATE_CURRENT
)
views.setOnClickPendingIntent(R.id.widget_view, configPendingIntent)
}
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
private fun setUpdateIntent(context: Context, appWidgetId: Int): PendingIntent? {
//Create update intent for refresh icon
val updateIntent = Intent(
context, CurrencyAppWidgetKotlin::class.java
).apply {
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, transformIntToArray(appWidgetId))
}
//add previous intent to this pending intent
return PendingIntent.getBroadcast(
context,
appWidgetId,
updateIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
private fun clickingIntent(
context: Context
): Intent {
val pair = repository.repository.getConversionPair()
val s1 = pair.first
val s2 = pair.second
return Intent(context, MainActivity::class.java).apply {
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
putExtra("parse_1", s1)
putExtra("parse_2", s2)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
}
companion object {
/**
* Unique job ID for this service.
*/
private const val JOB_ID = 1000
/**
* Convenience method for enqueuing work in to this service.
*/
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(context, WidgetServiceIntent::class.java, JOB_ID, work)
}
}
}

View File

@@ -1,20 +0,0 @@
package com.appttude.h_mal.easycc;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

View File

@@ -2,7 +2,6 @@ package com.appttude.h_mal.easycc.repository
import com.appttude.h_mal.easycc.data.network.api.BackupCurrencyApi 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.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.network.response.ResponseObject
import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
import com.appttude.h_mal.easycc.data.repository.Repository import com.appttude.h_mal.easycc.data.repository.Repository
@@ -29,8 +28,10 @@ class RepositoryNetworkTest{
@Mock @Mock
lateinit var api: CurrencyApi lateinit var api: CurrencyApi
@Mock @Mock
lateinit var apiBackup: BackupCurrencyApi lateinit var apiBackup: BackupCurrencyApi
@Mock @Mock
lateinit var prefs: PreferenceProvider lateinit var prefs: PreferenceProvider

View File

@@ -1,6 +1,5 @@
package com.appttude.h_mal.easycc.repository package com.appttude.h_mal.easycc.repository
import android.content.Context
import com.appttude.h_mal.easycc.data.network.api.BackupCurrencyApi 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.api.CurrencyApi
import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
@@ -19,8 +18,10 @@ class RepositoryStorageTest {
@Mock @Mock
lateinit var api: CurrencyApi lateinit var api: CurrencyApi
@Mock @Mock
lateinit var apiBackup: BackupCurrencyApi lateinit var apiBackup: BackupCurrencyApi
@Mock @Mock
lateinit var prefs: PreferenceProvider lateinit var prefs: PreferenceProvider

View File

@@ -6,10 +6,10 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.appttude.h_mal.easycc.data.network.response.ResponseObject import com.appttude.h_mal.easycc.data.network.response.ResponseObject
import com.appttude.h_mal.easycc.data.repository.Repository import com.appttude.h_mal.easycc.data.repository.Repository
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
import kotlinx.coroutines.runBlocking
import org.junit.Before
import com.appttude.h_mal.easycc.utils.observeOnce import com.appttude.h_mal.easycc.utils.observeOnce
import kotlinx.coroutines.runBlocking
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
@@ -48,7 +48,8 @@ class MainViewModelTest {
//WHEN //WHEN
Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne) Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne)
Mockito.`when`(bundle.getString("parse_2")).thenReturn(currencyTwo) Mockito.`when`(bundle.getString("parse_2")).thenReturn(currencyTwo)
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)).thenReturn(responseObject) Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
.thenReturn(responseObject)
//THEN //THEN
viewModel.initiate(bundle) viewModel.initiate(bundle)
@@ -71,7 +72,8 @@ class MainViewModelTest {
//WHEN //WHEN
Mockito.`when`(repository.getConversionPair()).thenReturn(pair) Mockito.`when`(repository.getConversionPair()).thenReturn(pair)
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)).thenReturn(responseObject) Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
.thenReturn(responseObject)
//THEN //THEN
viewModel.initiate(null) viewModel.initiate(null)
@@ -128,7 +130,6 @@ class MainViewModelTest {
} }
@Test @Test
fun setCurrencyName_validValues_successResponse() = runBlocking { fun setCurrencyName_validValues_successResponse() = runBlocking {
//GIVEN //GIVEN
@@ -139,7 +140,8 @@ class MainViewModelTest {
val responseObject = mock(ResponseObject::class.java) val responseObject = mock(ResponseObject::class.java)
//WHEN //WHEN
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)).thenReturn(responseObject) Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
.thenReturn(responseObject)
//THEN //THEN
viewModel.setCurrencyName(tag, currencyOne) viewModel.setCurrencyName(tag, currencyOne)
@@ -163,7 +165,8 @@ class MainViewModelTest {
val responseObject = mock(ResponseObject::class.java) val responseObject = mock(ResponseObject::class.java)
//WHEN //WHEN
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)).thenReturn(responseObject) Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
.thenReturn(responseObject)
//THEN //THEN
viewModel.setCurrencyName(tag, currencyOne) viewModel.setCurrencyName(tag, currencyOne)
@@ -185,7 +188,8 @@ class MainViewModelTest {
val responseObject = mock(ResponseObject::class.java) val responseObject = mock(ResponseObject::class.java)
//WHEN //WHEN
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo)).thenReturn(responseObject) Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
.thenReturn(responseObject)
//THEN //THEN
viewModel.setCurrencyName(tag, currencyOne) viewModel.setCurrencyName(tag, currencyOne)

View File

@@ -3,8 +3,8 @@ package com.appttude.h_mal.easycc.ui.widget
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.appttude.h_mal.easycc.data.repository.Repository import com.appttude.h_mal.easycc.data.repository.Repository
import com.appttude.h_mal.easycc.utils.observeOnce import com.appttude.h_mal.easycc.utils.observeOnce
import org.junit.Before
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
@@ -14,6 +14,7 @@ import org.mockito.MockitoAnnotations
private const val currencyOne = "AUD - Australian Dollar" private const val currencyOne = "AUD - Australian Dollar"
private const val currencyTwo = "GBP - British Pound" private const val currencyTwo = "GBP - British Pound"
class WidgetViewModelTest { class WidgetViewModelTest {
// Run tasks synchronously // Run tasks synchronously

View File

@@ -1,4 +1,4 @@
package com.appttude.h_mal.easycc package com.appttude.h_mal.easycc.utils
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner

View File

@@ -1,7 +1,6 @@
package com.appttude.h_mal.easycc.utils package com.appttude.h_mal.easycc.utils
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.appttude.h_mal.easycc.OneTimeObserver
fun <T> LiveData<T>.observeOnce(onChangeHandler: (T) -> Unit) { fun <T> LiveData<T>.observeOnce(onChangeHandler: (T) -> Unit) {
val observer = OneTimeObserver(handler = onChangeHandler) val observer = OneTimeObserver(handler = onChangeHandler)

View File

@@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.3.61' ext.kotlin_version = '1.4.10'
repositories { repositories {
jcenter() jcenter()
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.1' classpath 'com.android.tools.build:gradle:4.0.2'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View File

@@ -1,6 +1,6 @@
#Thu Mar 05 18:27:33 UTC 2020 #Sat Jun 12 22:27:25 BST 2021
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip