mirror of
https://github.com/hmalik144/EasyCC_Master.git
synced 2026-01-31 02:41:47 +00:00
Unit tests added
Query interceptor added Unit tests created - Repository test network - Repository test storage
This commit is contained in:
40
.idea/assetWizardSettings.xml
generated
40
.idea/assetWizardSettings.xml
generated
@@ -3,11 +3,49 @@
|
|||||||
<component name="WizardSettings">
|
<component name="WizardSettings">
|
||||||
<option name="children">
|
<option name="children">
|
||||||
<map>
|
<map>
|
||||||
<entry key="vectorWizard">
|
<entry key="imageWizard">
|
||||||
<value>
|
<value>
|
||||||
<PersistentState />
|
<PersistentState />
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry key="vectorWizard">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="vectorAssetStep">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="children">
|
||||||
|
<map>
|
||||||
|
<entry key="clipartAsset">
|
||||||
|
<value>
|
||||||
|
<PersistentState>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="url" value="jar:file:/C:/Program%20Files/Android/Android%20Studio/plugins/android/lib/android.jar!/images/material_design_icons/navigation/ic_refresh_black_24dp.xml" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
<option name="values">
|
||||||
|
<map>
|
||||||
|
<entry key="color" value="ffffff" />
|
||||||
|
<entry key="outputName" value="ic_refresh_white_24dp" />
|
||||||
|
<entry key="sourceFile" value="C:\Users\h_mal" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</PersistentState>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
BIN
.idea/caches/build_file_checksums.ser
generated
BIN
.idea/caches/build_file_checksums.ser
generated
Binary file not shown.
@@ -53,6 +53,7 @@ dependencies {
|
|||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
||||||
|
|
||||||
//Retrofit and GSON
|
//Retrofit and GSON
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
|
||||||
@@ -72,6 +73,10 @@ dependencies {
|
|||||||
implementation "org.kodein.di:kodein-di-generic-jvm:6.2.1"
|
implementation "org.kodein.di:kodein-di-generic-jvm:6.2.1"
|
||||||
implementation "org.kodein.di:kodein-di-framework-android-x:6.2.1"
|
implementation "org.kodein.di:kodein-di-framework-android-x:6.2.1"
|
||||||
|
|
||||||
|
//mockito and livedata testing
|
||||||
|
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||||
|
testImplementation 'androidx.arch.core:core-testing:2.1.0'
|
||||||
|
|
||||||
//Android Room
|
//Android Room
|
||||||
implementation "androidx.room:room-runtime:2.2.0-rc01"
|
implementation "androidx.room:room-runtime:2.2.0-rc01"
|
||||||
implementation "androidx.room:room-ktx:2.2.0-rc01"
|
implementation "androidx.room:room-ktx:2.2.0-rc01"
|
||||||
@@ -83,5 +88,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation "androidx.preference:preference-ktx:1.1.0"
|
implementation "androidx.preference:preference-ktx:1.1.0"
|
||||||
|
|
||||||
|
//mock websever for testing retrofit responses
|
||||||
|
testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0"
|
||||||
|
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
android:anyDensity="true" />
|
android:anyDensity="true" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".mvvm.AppClass"
|
android:name=".mvvm.application.AppClass"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.appwidget.provider"
|
android:name="android.appwidget.provider"
|
||||||
android:resource="@xml/currency_kotlin_app_widget_info" />
|
android:resource="@xml/currency_app_widget_info" />
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<activity android:name="com.appttude.h_mal.easycc.mvvm.ui.widget.CurrencyAppWidgetConfigureActivityKotlin">
|
<activity android:name="com.appttude.h_mal.easycc.mvvm.ui.widget.CurrencyAppWidgetConfigureActivityKotlin">
|
||||||
@@ -42,23 +42,7 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".legacy.MainActivityJava"></activity>
|
|
||||||
|
|
||||||
<receiver android:name=".legacy.CurrencyAppWidget">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="android.appwidget.provider"
|
|
||||||
android:resource="@xml/currency_app_widget_info" />
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<activity android:name=".legacy.CurrencyAppWidgetConfigureActivity">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package com.appttude.h_mal.easycc.mvvm
|
package com.appttude.h_mal.easycc.mvvm.application
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.Repository.Repository
|
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.network.NetworkConnectionInterceptor
|
import com.appttude.h_mal.easycc.mvvm.data.network.NetworkConnectionInterceptor
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.network.api.GetData
|
import com.appttude.h_mal.easycc.mvvm.data.network.QueryInterceptor
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.network.api.CurrencyApi
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
|
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
|
||||||
import com.appttude.h_mal.easycc.mvvm.ui.app.MainViewModelFactory
|
import com.appttude.h_mal.easycc.mvvm.ui.app.MainViewModelFactory
|
||||||
import com.appttude.h_mal.easycc.mvvm.ui.widget.WidgetViewModelFactory
|
import com.appttude.h_mal.easycc.mvvm.ui.widget.WidgetViewModelFactory
|
||||||
@@ -15,19 +16,18 @@ import org.kodein.di.generic.instance
|
|||||||
import org.kodein.di.generic.provider
|
import org.kodein.di.generic.provider
|
||||||
import org.kodein.di.generic.singleton
|
import org.kodein.di.generic.singleton
|
||||||
|
|
||||||
class AppClass : Application(), KodeinAware{
|
class AppClass : Application(), KodeinAware {
|
||||||
|
|
||||||
override val kodein = Kodein.lazy {
|
override val kodein by Kodein.lazy {
|
||||||
import(androidXModule(this@AppClass))
|
import(androidXModule(this@AppClass))
|
||||||
|
|
||||||
bind() from singleton { NetworkConnectionInterceptor(instance()) }
|
bind() from singleton { NetworkConnectionInterceptor(instance()) }
|
||||||
bind() from singleton { GetData(instance()) }
|
bind() from singleton { QueryInterceptor() }
|
||||||
|
bind() from singleton { CurrencyApi(instance(),instance()) }
|
||||||
bind() from singleton { PreferenceProvider(instance()) }
|
bind() from singleton { PreferenceProvider(instance()) }
|
||||||
bind() from singleton { Repository(instance(), instance(), instance()) }
|
bind() from singleton { RepositoryImpl(instance(), instance(), instance()) }
|
||||||
bind() from provider { MainViewModelFactory(instance()) }
|
bind() from provider { MainViewModelFactory(instance()) }
|
||||||
bind() from provider { WidgetViewModelFactory(instance()) }
|
bind() from provider { WidgetViewModelFactory(instance()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package com.appttude.h_mal.easycc.mvvm.data.Repository
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.appttude.h_mal.easycc.BuildConfig
|
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
|
|
||||||
import com.appttude.h_mal.easycc.R
|
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject
|
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.network.SafeApiRequest
|
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.network.api.GetData
|
|
||||||
|
|
||||||
class Repository (
|
|
||||||
private val api: GetData,
|
|
||||||
private val prefs: PreferenceProvider,
|
|
||||||
context: Context
|
|
||||||
): SafeApiRequest(){
|
|
||||||
|
|
||||||
var ccApiKey = BuildConfig.CC_API_KEY
|
|
||||||
|
|
||||||
private val appContext = context.applicationContext
|
|
||||||
|
|
||||||
suspend fun getData(s1: String, s2: String): ResponseObject?{
|
|
||||||
return apiRequest{ api.getCurrencyRate(convertPairsListToString(s1, s2),ccApiKey)}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getConversionPair(): List<String?> {
|
|
||||||
return prefs.getConversionPair()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setConversionPair(s1: String, s2: String){
|
|
||||||
prefs.saveConversionPair(s1, s2)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun convertPairsListToString(s1: String, s2: String): String = "${s1.substring(0,3)}_${s2.substring(0,3)}"
|
|
||||||
|
|
||||||
fun getArrayList(): Array<String> = appContext.resources.getStringArray(R.array.currency_arrays)
|
|
||||||
|
|
||||||
fun getWidgetConversionPairs(id: Int): List<String?> = prefs.getWidgetConversionPair(id)
|
|
||||||
|
|
||||||
fun setWidgetConversionPairs(s1: String, s2: String, id: Int) = prefs.saveWidgetConversionPair(s1, s2, id)
|
|
||||||
|
|
||||||
fun removeWidgetConversionPairs(id: Int) = prefs.removeWidgetConversion(id)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.appttude.h_mal.easycc.mvvm.data.network
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
|
||||||
|
class QueryInterceptor() : Interceptor {
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val original: Request = chain.request()
|
||||||
|
val originalHttpUrl: HttpUrl = original.url()
|
||||||
|
|
||||||
|
val url = originalHttpUrl.newBuilder()
|
||||||
|
.addQueryParameter("apikey", "a4f93cc2ff05dd772321")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// Add amended Url back to request
|
||||||
|
val requestBuilder: Request.Builder = original.newBuilder()
|
||||||
|
.url(url)
|
||||||
|
|
||||||
|
val request: Request = requestBuilder.build()
|
||||||
|
return chain.proceed(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,29 +1,67 @@
|
|||||||
package com.appttude.h_mal.easycc.mvvm.data.network
|
package com.appttude.h_mal.easycc.mvvm.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
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This abstract class extract objects from Retrofit [Response]
|
||||||
|
* or throws IOException if object does not exist
|
||||||
|
*/
|
||||||
|
private const val TAG = "SafeApiRequest"
|
||||||
abstract class SafeApiRequest {
|
abstract class SafeApiRequest {
|
||||||
|
|
||||||
suspend fun<T: Any> apiRequest(call: suspend () -> Response<T>) : T{
|
suspend fun <T : Any> responseUnwrap(
|
||||||
|
call: suspend () -> Response<T>
|
||||||
|
): T {
|
||||||
val response = call.invoke()
|
val response = call.invoke()
|
||||||
if(response.isSuccessful){
|
|
||||||
return response.body()!!
|
|
||||||
}else{
|
|
||||||
val error = response.errorBody()?.string()
|
|
||||||
|
|
||||||
val message = StringBuilder()
|
if (response.isSuccessful) {
|
||||||
error?.let{
|
// return the object within the response body
|
||||||
try{
|
return response.body()!!
|
||||||
message.append(JSONObject(it).getString("error"))
|
} else {
|
||||||
}catch(e: JSONException){ }
|
// the response was unsuccessful
|
||||||
message.append("\n")
|
// throw IOException error
|
||||||
}
|
throw IOException(errorMessage(response))
|
||||||
message.append("Error Code: ${response.code()}")
|
|
||||||
throw IOException(message.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <T> errorMessage(errorResponse: Response<T>): String {
|
||||||
|
val errorBody = errorResponse.errorBody()?.string()
|
||||||
|
val errorCode = "Error Code: ${errorResponse.code()}"
|
||||||
|
val errorMessageString = errorBody.getError()
|
||||||
|
|
||||||
|
//build a log message to log in console
|
||||||
|
val log = if (errorMessageString.isNullOrEmpty()){
|
||||||
|
errorCode
|
||||||
|
}else{
|
||||||
|
StringBuilder()
|
||||||
|
.append(errorCode)
|
||||||
|
.append("\n")
|
||||||
|
.append(errorMessageString)
|
||||||
|
.toString()
|
||||||
|
}
|
||||||
|
Log.e("Api Response Error", log)
|
||||||
|
|
||||||
|
//return error message
|
||||||
|
//if null return error code
|
||||||
|
return errorMessageString ?: errorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||||
|
private fun String?.getError(): String? {
|
||||||
|
this?.let {
|
||||||
|
try {
|
||||||
|
//convert response to JSON
|
||||||
|
//extract ["error"] from error body
|
||||||
|
return JSONObject(it).getString("error")
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
Log.e(TAG, e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.appttude.h_mal.easycc.mvvm.data.network.api
|
|||||||
|
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject
|
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.network.NetworkConnectionInterceptor
|
import com.appttude.h_mal.easycc.mvvm.data.network.NetworkConnectionInterceptor
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.network.QueryInterceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
@@ -10,15 +11,17 @@ import retrofit2.http.GET
|
|||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
|
||||||
interface GetData {
|
interface CurrencyApi {
|
||||||
|
|
||||||
companion object{
|
companion object{
|
||||||
operator fun invoke(
|
operator fun invoke(
|
||||||
networkConnectionInterceptor: NetworkConnectionInterceptor
|
networkConnectionInterceptor: NetworkConnectionInterceptor,
|
||||||
) : GetData{
|
queryInterceptor: QueryInterceptor
|
||||||
|
) : CurrencyApi{
|
||||||
|
|
||||||
val okkHttpclient = OkHttpClient.Builder()
|
val okkHttpclient = OkHttpClient.Builder()
|
||||||
.addNetworkInterceptor(networkConnectionInterceptor)
|
.addNetworkInterceptor(networkConnectionInterceptor)
|
||||||
|
.addInterceptor(queryInterceptor)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
@@ -26,11 +29,11 @@ interface GetData {
|
|||||||
.baseUrl("https://free.currencyconverterapi.com/api/v3/")
|
.baseUrl("https://free.currencyconverterapi.com/api/v3/")
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.build()
|
.build()
|
||||||
.create(GetData::class.java)
|
.create(CurrencyApi::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET("convert?")
|
@GET("convert?")
|
||||||
suspend fun getCurrencyRate(@Query("q") currency: String, @Query("apiKey") api: String): Response<ResponseObject>
|
suspend fun getCurrencyRate(@Query("q") currency: String): Response<ResponseObject>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,5 +7,5 @@ class ResponseObject(
|
|||||||
@SerializedName("query")
|
@SerializedName("query")
|
||||||
var query : Any,
|
var query : Any,
|
||||||
@SerializedName("results")
|
@SerializedName("results")
|
||||||
var results : Map<String, CurrencyObject>
|
var results : Map<String, CurrencyObject>?
|
||||||
)
|
)
|
||||||
@@ -32,11 +32,11 @@ class PreferenceProvider(
|
|||||||
).apply()
|
).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getConversionPair(): List<String?> {
|
fun getConversionPair(): Pair<String?, String?> {
|
||||||
val s1 = getLastConversionOne()
|
val s1 = getLastConversionOne()
|
||||||
val s2 = getLastConversionTwo()
|
val s2 = getLastConversionTwo()
|
||||||
|
|
||||||
return listOf(s1,s2)
|
return Pair(s1,s2)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLastConversionOne(): String? {
|
private fun getLastConversionOne(): String? {
|
||||||
@@ -57,11 +57,11 @@ class PreferenceProvider(
|
|||||||
).apply()
|
).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getWidgetConversionPair(id: Int): List<String?> {
|
fun getWidgetConversionPair(id: Int): Pair<String?, String?> {
|
||||||
val s1 = getWidgetLastConversionOne(id)
|
val s1 = getWidgetLastConversionOne(id)
|
||||||
val s2 = getWidgetLastConversionTwo(id)
|
val s2 = getWidgetLastConversionTwo(id)
|
||||||
|
|
||||||
return listOf(s1,s2)
|
return Pair(s1, s2)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getWidgetLastConversionOne(id: Int): String? {
|
private fun getWidgetLastConversionOne(id: Int): String? {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.appttude.h_mal.easycc.mvvm.data.repository
|
||||||
|
|
||||||
|
import com.appttude.h_mal.easycc.R
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.utils.convertPairsListToString
|
||||||
|
|
||||||
|
interface Repository {
|
||||||
|
|
||||||
|
suspend fun getData(s1: String, s2: String): ResponseObject
|
||||||
|
|
||||||
|
fun getConversionPair(): Pair<String?, String?>
|
||||||
|
|
||||||
|
fun setConversionPair(s1: String, s2: String)
|
||||||
|
|
||||||
|
fun getArrayList(): Array<String>
|
||||||
|
|
||||||
|
fun getWidgetConversionPairs(id: Int): Pair<String?, String?>
|
||||||
|
|
||||||
|
fun setWidgetConversionPairs(s1: String, s2: String, id: Int)
|
||||||
|
|
||||||
|
fun removeWidgetConversionPairs(id: Int)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.appttude.h_mal.easycc.mvvm.data.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.appttude.h_mal.easycc.BuildConfig
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
|
||||||
|
import com.appttude.h_mal.easycc.R
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.network.SafeApiRequest
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.network.api.CurrencyApi
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.utils.convertPairsListToString
|
||||||
|
|
||||||
|
|
||||||
|
class RepositoryImpl (
|
||||||
|
private val api: CurrencyApi,
|
||||||
|
private val prefs: PreferenceProvider,
|
||||||
|
context: Context
|
||||||
|
):Repository, SafeApiRequest(){
|
||||||
|
|
||||||
|
private val appContext = context.applicationContext
|
||||||
|
|
||||||
|
override suspend fun getData(s1: String, s2: String
|
||||||
|
): ResponseObject{
|
||||||
|
val currencyPair = convertPairsListToString(s1, s2)
|
||||||
|
return responseUnwrap{
|
||||||
|
api.getCurrencyRate(currencyPair)}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getConversionPair(): Pair<String?, String?> {
|
||||||
|
return prefs.getConversionPair()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setConversionPair(s1: String, s2: String){
|
||||||
|
prefs.saveConversionPair(s1, s2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getArrayList(): Array<String> =
|
||||||
|
appContext.resources.getStringArray(R.array.currency_arrays)
|
||||||
|
|
||||||
|
override fun getWidgetConversionPairs(id: Int): Pair<String?, String?> =
|
||||||
|
prefs.getWidgetConversionPair(id)
|
||||||
|
|
||||||
|
override fun setWidgetConversionPairs(s1: String, s2: String, id: Int) =
|
||||||
|
prefs.saveWidgetConversionPair(s1, s2, id)
|
||||||
|
|
||||||
|
override fun removeWidgetConversionPairs(id: Int) =
|
||||||
|
prefs.removeWidgetConversion(id)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,15 +7,14 @@ 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 android.widget.Toast
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
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
|
||||||
import com.appttude.h_mal.easycc.utils.DisplayToast
|
import com.appttude.h_mal.easycc.mvvm.utils.DisplayToast
|
||||||
import com.appttude.h_mal.easycc.utils.clearEditText
|
import com.appttude.h_mal.easycc.mvvm.utils.clearEditText
|
||||||
import com.appttude.h_mal.easycc.utils.hideView
|
import com.appttude.h_mal.easycc.mvvm.utils.hideView
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import org.kodein.di.KodeinAware
|
import org.kodein.di.KodeinAware
|
||||||
import org.kodein.di.android.kodein
|
import org.kodein.di.android.kodein
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ package com.appttude.h_mal.easycc.mvvm.ui.app
|
|||||||
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 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
|
||||||
import java.text.NumberFormat
|
|
||||||
|
|
||||||
class MainViewModel(
|
class MainViewModel(
|
||||||
private val repository: Repository
|
private val repository: Repository
|
||||||
@@ -18,14 +17,20 @@ class MainViewModel(
|
|||||||
private val defaultValue by lazy { repository.getArrayList()[0] }
|
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[0] ?: defaultValue
|
var rateIdFrom: String? = conversionPairs.first ?: defaultValue
|
||||||
var rateIdTo: String? = conversionPairs[1] ?: defaultValue
|
var rateIdTo: String? = conversionPairs.second ?: defaultValue
|
||||||
|
|
||||||
var topVal: String? = null
|
var topVal: String? = null
|
||||||
var bottomVal: String? = null
|
var bottomVal: String? = null
|
||||||
|
|
||||||
var rateListener: RateListener? = null
|
var rateListener: RateListener? = null
|
||||||
|
|
||||||
|
//operation results livedata based on outcome of operation
|
||||||
|
val operationSuccess = MutableLiveData<Boolean>()
|
||||||
|
val operationFailed = MutableLiveData<String>()
|
||||||
|
|
||||||
|
val currencyRate = MutableLiveData<Double>()
|
||||||
|
|
||||||
private var conversionRate: Double = 0.00
|
private var conversionRate: Double = 0.00
|
||||||
|
|
||||||
fun getExchangeRate(){
|
fun getExchangeRate(){
|
||||||
@@ -40,15 +45,17 @@ class MainViewModel(
|
|||||||
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()
|
rateListener?.onSuccess()
|
||||||
conversionRate = it.value
|
conversionRate = it.value
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
rateListener?.onFailure("Failed to retrieve rate")
|
|
||||||
}catch(e: IOException){
|
}catch(e: IOException){
|
||||||
rateListener?.onFailure(e.message!!)
|
rateListener?.onFailure(e.message ?: "Currency Retrieval failed")
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
|
rateListener?.onFailure("Failed to retrieve rate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package com.appttude.h_mal.easycc.mvvm.ui.app
|
|||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.Repository.Repository
|
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class MainViewModelFactory (
|
class MainViewModelFactory (
|
||||||
private val repository: Repository
|
private val repository: RepositoryImpl
|
||||||
): ViewModelProvider.NewInstanceFactory(){
|
): ViewModelProvider.NewInstanceFactory(){
|
||||||
|
|
||||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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.CurrencyAppWidgetConfigureBinding
|
import com.appttude.h_mal.easycc.databinding.CurrencyAppWidgetConfigureBinding
|
||||||
import com.appttude.h_mal.easycc.mvvm.ui.app.RateListener
|
import com.appttude.h_mal.easycc.mvvm.ui.app.RateListener
|
||||||
import com.appttude.h_mal.easycc.utils.DisplayToast
|
import com.appttude.h_mal.easycc.mvvm.utils.DisplayToast
|
||||||
import org.kodein.di.KodeinAware
|
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
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ package com.appttude.h_mal.easycc.mvvm.ui.widget
|
|||||||
import android.app.PendingIntent
|
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 android.widget.RemoteViews
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.appttude.h_mal.easycc.legacy.MainActivityJava
|
|
||||||
import com.appttude.h_mal.easycc.R
|
import com.appttude.h_mal.easycc.R
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.Repository.Repository
|
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.network.NetworkConnectionInterceptor
|
import com.appttude.h_mal.easycc.mvvm.ui.app.MainActivity
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.network.api.GetData
|
import com.appttude.h_mal.easycc.mvvm.utils.transformIntToArray
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -21,34 +21,51 @@ import org.kodein.di.LateInitKodein
|
|||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of App Widget functionality.
|
* Implementation of App Widget functionality.
|
||||||
* App Widget Configuration implemented in [CurrencyAppWidgetConfigureActivityKotlin]
|
* App Widget Configuration implemented in [CurrencyAppWidgetConfigureActivityKotlin]
|
||||||
*/
|
*/
|
||||||
|
private const val TAG = "CurrencyAppWidgetKotlin"
|
||||||
class CurrencyAppWidgetKotlin : AppWidgetProvider() {
|
class CurrencyAppWidgetKotlin : AppWidgetProvider() {
|
||||||
|
|
||||||
|
//DI with kodein to use in CurrencyAppWidgetKotlin
|
||||||
private val kodein = LateInitKodein()
|
private val kodein = LateInitKodein()
|
||||||
private val repository : Repository by kodein.instance()
|
private val repository : RepositoryImpl by kodein.instance()
|
||||||
|
|
||||||
|
//update trigger either on timed update or from from first start
|
||||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||||
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
|
Log.i(TAG,"onUpdate() appWidgetIds = ${appWidgetIds.size}")
|
||||||
// There may be multiple widgets active, so update all of them
|
// There may be multiple widgets active, so update all of them
|
||||||
for (appWidgetId in appWidgetIds) {
|
for (appWidgetId in appWidgetIds) {
|
||||||
updateAppWidget(context, appWidgetManager, appWidgetId)
|
updateAppWidget(context, appWidgetManager, appWidgetId)
|
||||||
}
|
}
|
||||||
|
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||||
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
|
|
||||||
// When the user deletes the widget, delete the preference associated with it.
|
// When the user deletes the widget, delete the preference associated with it.
|
||||||
for (appWidgetId in appWidgetIds) {
|
for (appWidgetId in appWidgetIds) {
|
||||||
repository.removeWidgetConversionPairs(appWidgetId)
|
repository.removeWidgetConversionPairs(appWidgetId)
|
||||||
}
|
}
|
||||||
|
super.onDeleted(context, appWidgetIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEnabled(context: Context) {
|
override fun onEnabled(context: Context) {
|
||||||
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
|
|
||||||
// Enter relevant functionality for when the first widget is created
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
if (context == null){ return }
|
||||||
|
kodein.baseKodein = (context.applicationContext as KodeinAware).kodein
|
||||||
|
|
||||||
|
super.onReceive(context, intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisabled(context: Context) {
|
override fun onDisabled(context: Context) {
|
||||||
@@ -57,58 +74,75 @@ class CurrencyAppWidgetKotlin : AppWidgetProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
|
private fun updateAppWidget(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int) {
|
||||||
//todo: get value from repository
|
|
||||||
val stringList = repository.getWidgetConversionPairs(appWidgetId)
|
val stringList = repository.getWidgetConversionPairs(appWidgetId)
|
||||||
val s1 = stringList[0]
|
val s1 = stringList.first
|
||||||
val s2 = stringList[1]
|
val s2 = stringList.second
|
||||||
|
|
||||||
// Construct the RemoteViews object
|
// Construct the RemoteViews object
|
||||||
val views = RemoteViews(context.packageName, R.layout.currency_app_widget)
|
val views = RemoteViews(context.packageName, R.layout.currency_app_widget)
|
||||||
views.setTextViewText(R.id.exchangeName, "Rates")
|
|
||||||
views.setTextViewText(R.id.exchangeRate, "not set")
|
|
||||||
|
|
||||||
//todo: async task to get rate
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
try {
|
try {
|
||||||
val response = repository.getData(s1!!.substring(0,3),s2!!.substring(0,3))
|
val response = repository.getData(s1!!.substring(0,3),s2!!.substring(0,3))
|
||||||
|
|
||||||
response?.results?.iterator()?.next()?.value?.let {
|
response.results?.iterator()?.next()?.value?.let {
|
||||||
val titleString = "${it.fr}${it.to}"
|
val titleString = "${it.fr}${it.to}"
|
||||||
views.setTextViewText(R.id.exchangeName, titleString)
|
views.setTextViewText(R.id.exchangeName, titleString)
|
||||||
views.setTextViewText(R.id.exchangeRate, it.value.toString())
|
views.setTextViewText(R.id.exchangeRate, it.value.toString())
|
||||||
}
|
}
|
||||||
}catch (io : IOException){
|
}catch (io : IOException){
|
||||||
|
Log.i("WidgetClass",io.message ?: "Failed")
|
||||||
Toast.makeText(context,io.message, Toast.LENGTH_LONG).show()
|
Toast.makeText(context,io.message, Toast.LENGTH_LONG).show()
|
||||||
}finally {
|
}finally {
|
||||||
// Instruct the widget manager to update the widget
|
setUpdateIntent(context, appWidgetId).let {
|
||||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
//set the pending intent to the icon
|
||||||
|
views.setImageViewResource(R.id.refresh_icon, R.drawable.ic_refresh_white_24dp)
|
||||||
val opacity = 0.3f //opacity = 0: fully transparent, opacity = 1: no transparancy
|
views.setOnClickPendingIntent(R.id.refresh_icon, it)
|
||||||
val backgroundColor = 0x000000 //background color (here black)
|
|
||||||
|
|
||||||
views.setInt(R.id.widget_view, "setBackgroundColor", (opacity * 0xFF).toInt() shl 24 or backgroundColor)
|
|
||||||
|
|
||||||
val clickIntentTemplate = Intent(context, MainActivityJava::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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val configPendingIntent = PendingIntent.getActivity(context, 0, clickIntentTemplate, PendingIntent.FLAG_UPDATE_CURRENT)
|
val clickIntentTemplate = clickingIntent(context, s1, s2)
|
||||||
|
|
||||||
|
val configPendingIntent =
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
context, appWidgetId, clickIntentTemplate,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
views.setOnClickPendingIntent(R.id.widget_view, configPendingIntent)
|
views.setOnClickPendingIntent(R.id.widget_view, configPendingIntent)
|
||||||
|
|
||||||
|
// Instruct the widget manager to update the widget
|
||||||
|
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupRepository(context: Context): Repository {
|
private fun setUpdateIntent(context: Context, appWidgetId: Int): PendingIntent? {
|
||||||
val networkInterceptor = NetworkConnectionInterceptor(context)
|
//Create update intent for refresh icon
|
||||||
val getData = GetData(networkInterceptor)
|
val updateIntent = Intent(
|
||||||
val prefs = PreferenceProvider(context)
|
context, CurrencyAppWidgetKotlin::class.java).apply {
|
||||||
return Repository(getData,prefs,context)
|
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,
|
||||||
|
s1: String?, s2: String?
|
||||||
|
): Intent {
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import android.appwidget.AppWidgetManager
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.appttude.h_mal.easycc.R
|
import com.appttude.h_mal.easycc.R
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.utils.transformIntToArray
|
||||||
import kotlinx.android.synthetic.main.confirm_dialog.*
|
import kotlinx.android.synthetic.main.confirm_dialog.*
|
||||||
|
|
||||||
|
|
||||||
/*
|
/**
|
||||||
widget for when submitting the completed selections
|
* Dialog created when submitting the completed selections
|
||||||
|
* in [CurrencyAppWidgetConfigureActivityKotlin]
|
||||||
*/
|
*/
|
||||||
class WidgetSubmitDialog(
|
class WidgetSubmitDialog(
|
||||||
private val activity: Activity,
|
private val activity: Activity,
|
||||||
@@ -21,26 +23,32 @@ class WidgetSubmitDialog(
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.confirm_dialog)
|
setContentView(R.layout.confirm_dialog)
|
||||||
|
// layer behind dialog to be transparent
|
||||||
// requestWindowFeature(Window.FEATURE_NO_TITLE)
|
|
||||||
window!!.setBackgroundDrawableResource(android.R.color.transparent)
|
window!!.setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
|
// Dialog cannot be cancelled by clicking away
|
||||||
setCancelable(false)
|
setCancelable(false)
|
||||||
|
|
||||||
//todo: amend widget text
|
|
||||||
confirm_text.text = StringBuilder().append("Create widget for ")
|
confirm_text.text = StringBuilder().append("Create widget for ")
|
||||||
.append(viewModel.getWidgetStringName())
|
.append(viewModel.getWidgetStringName())
|
||||||
.append("?").toString()
|
.append("?").toString()
|
||||||
|
|
||||||
confirm_yes.setOnClickListener {
|
confirm_yes.setOnClickListener {
|
||||||
viewModel.setWidgetStored()
|
// It is the responsibility of the configuration activity to update the app widget
|
||||||
|
// Send update broadcast to widget app class
|
||||||
|
Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
|
||||||
|
null,
|
||||||
|
context,
|
||||||
|
CurrencyAppWidgetKotlin::class.java).apply {
|
||||||
|
// Save current widget pairs
|
||||||
|
viewModel.setWidgetStored()
|
||||||
|
// Put current app widget ID into extras and send broadcast
|
||||||
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, transformIntToArray(appWidgetId) )
|
||||||
|
activity.sendBroadcast(this)
|
||||||
|
}
|
||||||
|
|
||||||
val intent = Intent(context, CurrencyAppWidgetKotlin::class.java)
|
|
||||||
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
|
||||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, IntArray(appWidgetId))
|
|
||||||
context.sendBroadcast(intent)
|
|
||||||
|
|
||||||
// Make sure we pass back the original appWidgetId
|
// Make sure we pass back the original appWidgetId
|
||||||
val resultValue = Intent()
|
val resultValue = activity.intent
|
||||||
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||||
activity.setResult(Activity.RESULT_OK, resultValue)
|
activity.setResult(Activity.RESULT_OK, resultValue)
|
||||||
activity.finish()
|
activity.finish()
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import android.view.View
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
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.RepositoryImpl
|
||||||
import com.appttude.h_mal.easycc.mvvm.ui.app.RateListener
|
import com.appttude.h_mal.easycc.mvvm.ui.app.RateListener
|
||||||
|
|
||||||
class WidgetViewModel(
|
class WidgetViewModel(
|
||||||
private val repository: Repository
|
private val repository: RepositoryImpl
|
||||||
) : ViewModel(){
|
) : ViewModel(){
|
||||||
|
|
||||||
var rateListener: RateListener? = null
|
var rateListener: RateListener? = null
|
||||||
@@ -22,10 +22,9 @@ class WidgetViewModel(
|
|||||||
appWidgetId = appId
|
appWidgetId = appId
|
||||||
val widgetString = getWidgetStored(appId)
|
val widgetString = getWidgetStored(appId)
|
||||||
|
|
||||||
if (widgetString.isNotEmpty()){
|
rateIdFrom.value = widgetString.first
|
||||||
rateIdFrom.value = widgetString[0]
|
rateIdTo.value = widgetString.second
|
||||||
rateIdTo.value = widgetString[1]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectCurrencyOnClick(view: View){
|
fun selectCurrencyOnClick(view: View){
|
||||||
@@ -80,15 +79,5 @@ class WidgetViewModel(
|
|||||||
|
|
||||||
private fun String.trimToThree() = this.substring(0,3)
|
private fun String.trimToThree() = this.substring(0,3)
|
||||||
|
|
||||||
private fun arrayEntry(s: String?): String? {
|
|
||||||
val strings = repository.getArrayList()
|
|
||||||
var returnString: String? = strings[0]
|
|
||||||
for (string in strings) {
|
|
||||||
if (s == string.substring(0, 3)) {
|
|
||||||
returnString = string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return returnString
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,11 @@ package com.appttude.h_mal.easycc.mvvm.ui.widget
|
|||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.appttude.h_mal.easycc.mvvm.data.Repository.Repository
|
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class WidgetViewModelFactory (
|
class WidgetViewModelFactory (
|
||||||
private val repository: Repository
|
private val repository: RepositoryImpl
|
||||||
): ViewModelProvider.NewInstanceFactory(){
|
): ViewModelProvider.NewInstanceFactory(){
|
||||||
|
|
||||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.appttude.h_mal.easycc.mvvm.utils
|
||||||
|
|
||||||
|
fun transformIntToArray(int: Int): IntArray{
|
||||||
|
return intArrayOf(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.trimToThree(): String{
|
||||||
|
if (this.length > 3){
|
||||||
|
return this.substring(0, 3)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertPairsListToString(s1: String, s2: String): String =
|
||||||
|
"${s1.trimToThree()}_${s2.trimToThree()}"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.appttude.h_mal.easycc.utils
|
package com.appttude.h_mal.easycc.mvvm.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -15,4 +15,4 @@ fun View.hideView(vis : Boolean){
|
|||||||
|
|
||||||
fun Context.DisplayToast(message: String){
|
fun Context.DisplayToast(message: String){
|
||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
5
app/src/main/res/drawable/ic_refresh_white_24dp.xml
Normal file
5
app/src/main/res/drawable/ic_refresh_white_24dp.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
||||||
|
</vector>
|
||||||
@@ -15,15 +15,17 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="6dp"
|
android:layout_margin="12dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/confirm_text"
|
android:id="@+id/confirm_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
android:layout_margin="8dp"
|
android:layout_marginLeft="12dp"
|
||||||
|
android:layout_marginRight="36dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
android:text="Create widget for AUDGBP?"
|
android:text="Create widget for AUDGBP?"
|
||||||
android:textColor="@color/colour_five" />
|
android:textColor="@color/colour_five" />
|
||||||
|
|
||||||
@@ -37,7 +39,11 @@
|
|||||||
android:id="@+id/confirm_yes"
|
android:id="@+id/confirm_yes"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:paddingLeft="12dp"
|
||||||
|
android:paddingRight="12dp"
|
||||||
|
android:textStyle="bold"
|
||||||
android:text="@android:string/yes"
|
android:text="@android:string/yes"
|
||||||
android:textColor="@color/colour_five" />
|
android:textColor="@color/colour_five" />
|
||||||
|
|
||||||
@@ -46,6 +52,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
|
android:textStyle="bold"
|
||||||
android:text="@android:string/no"
|
android:text="@android:string/no"
|
||||||
android:textColor="@color/colour_five" />
|
android:textColor="@color/colour_five" />
|
||||||
|
|
||||||
|
|||||||
@@ -2,23 +2,38 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/widget_view"
|
android:id="@+id/widget_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
tools:layout_width="110dp"
|
||||||
android:layout_height="72dp"
|
android:layout_height="72dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:background="#4D000000">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
<TextView
|
|
||||||
android:id="@+id/exchangeName"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginBottom="3dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:autoSizeMaxTextSize="100sp"
|
android:layout_marginBottom="3dp">
|
||||||
android:autoSizeMinTextSize="8sp"
|
<TextView
|
||||||
android:autoSizeStepGranularity="2sp"
|
android:id="@+id/exchangeName"
|
||||||
android:autoSizeTextType="uniform"
|
android:layout_width="match_parent"
|
||||||
android:gravity="center"
|
android:layout_height="match_parent"
|
||||||
android:textColor="#ffffff"
|
android:gravity="center"
|
||||||
tools:text="AUDGBP" />
|
android:autoSizeMaxTextSize="100sp"
|
||||||
|
android:autoSizeMinTextSize="6sp"
|
||||||
|
android:autoSizeStepGranularity="2sp"
|
||||||
|
android:autoSizeTextType="uniform"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:text="Rate not set"
|
||||||
|
tools:text="AUDGBP" />
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/refresh_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:src="@drawable/ic_refresh_white_24dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignBottom="@id/exchangeName"
|
||||||
|
android:adjustViewBounds="true"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/exchangeRate"
|
android:id="@+id/exchangeRate"
|
||||||
@@ -32,5 +47,5 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textColor="#ffffff"
|
android:textColor="#ffffff"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
tools:text="0.56" />
|
tools:text="0.526462" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:configure="com.appttude.h_mal.easycc.legacy.CurrencyAppWidgetConfigureActivity"
|
android:configure="com.appttude.h_mal.easycc.mvvm.ui.widget.CurrencyAppWidgetConfigureActivityKotlin"
|
||||||
android:initialKeyguardLayout="@layout/currency_app_widget"
|
android:initialKeyguardLayout="@layout/currency_app_widget"
|
||||||
android:initialLayout="@layout/currency_app_widget"
|
android:initialLayout="@layout/currency_app_widget"
|
||||||
android:minHeight="40dp"
|
android:minHeight="40dp"
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
android:minResizeWidth="40dp"
|
android:minResizeWidth="40dp"
|
||||||
android:previewImage="@drawable/easyycc_widget_preview"
|
android:previewImage="@drawable/easyycc_widget_preview"
|
||||||
android:resizeMode="horizontal"
|
android:resizeMode="horizontal"
|
||||||
android:updatePeriodMillis="86400000"
|
android:updatePeriodMillis="21600000"
|
||||||
android:widgetCategory="home_screen">
|
android:widgetCategory="home_screen">
|
||||||
|
|
||||||
</appwidget-provider>
|
</appwidget-provider>
|
||||||
@@ -7,5 +7,5 @@
|
|||||||
android:minHeight="40dp"
|
android:minHeight="40dp"
|
||||||
android:previewImage="@drawable/example_appwidget_preview"
|
android:previewImage="@drawable/example_appwidget_preview"
|
||||||
android:resizeMode="horizontal|vertical"
|
android:resizeMode="horizontal|vertical"
|
||||||
android:updatePeriodMillis="86400000"
|
android:updatePeriodMillis="3600000"
|
||||||
android:widgetCategory="home_screen|keyguard"></appwidget-provider>
|
android:widgetCategory="home_screen|keyguard"/>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.appttude.h_mal.easycc.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.appttude.h_mal.easycc.BuildConfig
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.network.api.CurrencyApi
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.network.response.ResponseObject
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.repository.Repository
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.utils.convertPairsListToString
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
|
||||||
|
class RepositoryNetworkTest{
|
||||||
|
|
||||||
|
lateinit var repository: Repository
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
lateinit var api: CurrencyApi
|
||||||
|
@Mock
|
||||||
|
lateinit var prefs: PreferenceProvider
|
||||||
|
@Mock
|
||||||
|
lateinit var context: Context
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
repository = RepositoryImpl(api, prefs, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getRateFromApi_positiveResponse() = runBlocking {
|
||||||
|
//GIVEN - Create query string
|
||||||
|
val s1 = "AUD - Australian Dollar"
|
||||||
|
val s2 = "GBP - British Pound"
|
||||||
|
val query = convertPairsListToString(s1, s2)
|
||||||
|
//create a successful retrofit response
|
||||||
|
val mockCurrencyResponse = mock(ResponseObject::class.java)
|
||||||
|
val re = Response.success(mockCurrencyResponse)
|
||||||
|
|
||||||
|
//WHEN - loginApiRequest to return a successful response
|
||||||
|
Mockito.`when`(api.getCurrencyRate(query)).thenReturn(re)
|
||||||
|
|
||||||
|
//THEN - the unwrapped login response contains the correct values
|
||||||
|
val currencyResponse = repository.getData(s1,s2)
|
||||||
|
assertNotNull(currencyResponse)
|
||||||
|
assertEquals(currencyResponse, mockCurrencyResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loginUser_negativeResponse() = runBlocking {
|
||||||
|
//GIVEN
|
||||||
|
val s1 = "AUD - Australian Dollar"
|
||||||
|
val s2 = "GBP - British Pound"
|
||||||
|
val query = convertPairsListToString(s1, s2)
|
||||||
|
//mock retrofit error response
|
||||||
|
val mockBody = mock(ResponseBody::class.java)
|
||||||
|
val mockRaw = mock(okhttp3.Response::class.java)
|
||||||
|
val re = Response.error<String>(mockBody, mockRaw)
|
||||||
|
|
||||||
|
//WHEN
|
||||||
|
Mockito.`when`(api.getCurrencyRate(query)).thenAnswer { re }
|
||||||
|
|
||||||
|
//THEN - assert exception is not null
|
||||||
|
val ioExceptionReturned = assertFailsWith<IOException> {
|
||||||
|
repository.getData(s1, s2)
|
||||||
|
}
|
||||||
|
assertNotNull(ioExceptionReturned)
|
||||||
|
assertNotNull(ioExceptionReturned.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.appttude.h_mal.easycc.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.network.api.CurrencyApi
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.prefs.PreferenceProvider
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.repository.Repository
|
||||||
|
import com.appttude.h_mal.easycc.mvvm.data.repository.RepositoryImpl
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class RepositoryStorageTest {
|
||||||
|
|
||||||
|
lateinit var repository: Repository
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
lateinit var api: CurrencyApi
|
||||||
|
@Mock
|
||||||
|
lateinit var prefs: PreferenceProvider
|
||||||
|
@Mock
|
||||||
|
lateinit var context: Context
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
repository = RepositoryImpl(api, prefs, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun saveAndRetrieve_PositiveResponse() {
|
||||||
|
//GIVEN
|
||||||
|
val s1 = "AUD - Australian Dollar"
|
||||||
|
val s2 = "GBP - British Pound"
|
||||||
|
val pair = Pair(s1, s2)
|
||||||
|
repository.setConversionPair(s1, s2)
|
||||||
|
|
||||||
|
//WHEN
|
||||||
|
Mockito.`when`(prefs.getConversionPair()).thenReturn(pair)
|
||||||
|
|
||||||
|
//THEN
|
||||||
|
assertEquals(pair, repository.getConversionPair())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun saveAndRetrieveCredentials_PositiveResponse() {
|
||||||
|
//GIVEN
|
||||||
|
val s1 = "AUD - Australian Dollar"
|
||||||
|
val s2 = "GBP - British Pound"
|
||||||
|
val id = 1234
|
||||||
|
val pair = Pair(s1, s2)
|
||||||
|
repository.setWidgetConversionPairs("forename", "Surname", id)
|
||||||
|
|
||||||
|
//WHEN
|
||||||
|
Mockito.`when`(prefs.getWidgetConversionPair(id)).thenReturn(pair)
|
||||||
|
|
||||||
|
//THEN
|
||||||
|
assertEquals(pair, repository.getWidgetConversionPairs(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user